@velocity-exchange/vaults-sdk 0.0.1
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 +3 -0
- package/README.md +152 -0
- package/cli/cli.ts +751 -0
- package/cli/commands/adminDeleteFeeUpdate.ts +73 -0
- package/cli/commands/adminInitFeeUpdate.ts +73 -0
- package/cli/commands/adminUpdateVaultClass.ts +49 -0
- package/cli/commands/applyProfitShare.ts +139 -0
- package/cli/commands/decodeLogs.ts +98 -0
- package/cli/commands/deposit.ts +98 -0
- package/cli/commands/deriveVaultAddress.ts +14 -0
- package/cli/commands/forceWithdraw.ts +56 -0
- package/cli/commands/forceWithdrawAll.ts +142 -0
- package/cli/commands/index.ts +28 -0
- package/cli/commands/initVault.ts +227 -0
- package/cli/commands/initVaultDepositor.ts +42 -0
- package/cli/commands/listDepositorsForVault.ts +32 -0
- package/cli/commands/managerApplyProfitShare.ts +32 -0
- package/cli/commands/managerBorrow.ts +77 -0
- package/cli/commands/managerCancelWithdraw.ts +30 -0
- package/cli/commands/managerDeposit.ts +45 -0
- package/cli/commands/managerRepay.ts +94 -0
- package/cli/commands/managerRequestWithdraw.ts +86 -0
- package/cli/commands/managerUpdateBorrow.ts +56 -0
- package/cli/commands/managerUpdateFees.ts +156 -0
- package/cli/commands/managerUpdateMarginTradingEnabled.ts +32 -0
- package/cli/commands/managerUpdatePoolId.ts +36 -0
- package/cli/commands/managerUpdateVault.ts +210 -0
- package/cli/commands/managerUpdateVaultDelegate.ts +43 -0
- package/cli/commands/managerUpdateVaultManager.ts +77 -0
- package/cli/commands/managerWithdraw.ts +30 -0
- package/cli/commands/requestWithdraw.ts +58 -0
- package/cli/commands/vaultDeposit.ts +42 -0
- package/cli/commands/vaultInvariantChecks.ts +407 -0
- package/cli/commands/vaultWithdraw.ts +42 -0
- package/cli/commands/viewVault.ts +50 -0
- package/cli/commands/viewVaultDepositor.ts +36 -0
- package/cli/commands/withdraw.ts +40 -0
- package/cli/ledgerWallet.test.ts +49 -0
- package/cli/ledgerWallet.ts +111 -0
- package/cli/utils.ts +389 -0
- package/package.json +48 -0
- package/src/accountSubscribers/index.ts +2 -0
- package/src/accountSubscribers/pollingVaultDepositorSubscriber.ts +69 -0
- package/src/accountSubscribers/pollingVaultSubscriber.ts +63 -0
- package/src/accountSubscribers/pollingVaultsProgramAccountSubscriber.ts +114 -0
- package/src/accounts/index.ts +2 -0
- package/src/accounts/vaultAccount.ts +255 -0
- package/src/accounts/vaultDepositorAccount.ts +77 -0
- package/src/accounts/vaultsProgramAccount.ts +38 -0
- package/src/addresses.ts +114 -0
- package/src/constants/index.ts +15 -0
- package/src/idl/drift_vaults.json +5698 -0
- package/src/index.ts +11 -0
- package/src/math/index.ts +2 -0
- package/src/math/vault.ts +71 -0
- package/src/math/vaultDepositor.ts +90 -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 +6211 -0
- package/src/types/types.ts +336 -0
- package/src/utils.ts +74 -0
- package/src/vaultClient.ts +3666 -0
- package/tsconfig.json +24 -0
- package/velocity-exchange-vaults-sdk-0.0.1.tgz +0 -0
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@velocity-exchange/vaults-sdk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "lib/index.js",
|
|
5
|
+
"types": "lib/index.d.ts",
|
|
6
|
+
"directories": {
|
|
7
|
+
"lib": "lib"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@metaplex-foundation/js": "0.20.1",
|
|
11
|
+
"@velocity-exchange/sdk": "0.0.3",
|
|
12
|
+
"commander": "11.1.0",
|
|
13
|
+
"dotenv": "16.4.5",
|
|
14
|
+
"strict-event-emitter-types": "2.0.0",
|
|
15
|
+
"ts-node": "10.9.2",
|
|
16
|
+
"typescript": "5.6.3"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@coral-xyz/anchor": "npm:@anchor-lang/core@1.0.1",
|
|
20
|
+
"@solana/web3.js": "1.98.0"
|
|
21
|
+
},
|
|
22
|
+
"optionalDependencies": {
|
|
23
|
+
"@ledgerhq/hw-app-solana": "7.2.4",
|
|
24
|
+
"@ledgerhq/hw-transport": "6.31.4",
|
|
25
|
+
"@ledgerhq/hw-transport-node-hid": "6.29.5",
|
|
26
|
+
"@solana/wallet-adapter-ledger": "0.9.25"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@coral-xyz/anchor": "npm:@anchor-lang/core@1.0.1",
|
|
30
|
+
"@types/bn.js": "5.1.6"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": "^24.0.0"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"cli": "ts-node cli/cli.ts",
|
|
37
|
+
"clean": "rm -rf lib",
|
|
38
|
+
"build": "yarn clean && tsc"
|
|
39
|
+
},
|
|
40
|
+
"resolutions": {
|
|
41
|
+
"@types/bn.js": "5.1.6",
|
|
42
|
+
"rpc-websockets": "10.0.0",
|
|
43
|
+
"@solana/web3.js": "1.98.0",
|
|
44
|
+
"@drift-labs/sdk": "npm:@velocity-exchange/sdk@0.0.3",
|
|
45
|
+
"@velocity-exchange/sdk": "0.0.3",
|
|
46
|
+
"@coral-xyz/anchor": "npm:@anchor-lang/core@1.0.1"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VaultDepositor,
|
|
3
|
+
VaultDepositorAccountEvents,
|
|
4
|
+
VaultDepositorAccountSubscriber,
|
|
5
|
+
} from '../types/types';
|
|
6
|
+
import { PollingVaultsProgramAccountSubscriber } from './pollingVaultsProgramAccountSubscriber';
|
|
7
|
+
|
|
8
|
+
export class PollingVaultDepositorSubscriber
|
|
9
|
+
extends PollingVaultsProgramAccountSubscriber<
|
|
10
|
+
VaultDepositor,
|
|
11
|
+
VaultDepositorAccountEvents
|
|
12
|
+
>
|
|
13
|
+
implements VaultDepositorAccountSubscriber
|
|
14
|
+
{
|
|
15
|
+
async addToAccountLoader(): Promise<void> {
|
|
16
|
+
if (this.callbackId) {
|
|
17
|
+
console.log(
|
|
18
|
+
'Account for vault depositor already added to account loader'
|
|
19
|
+
);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.callbackId = await this.accountLoader.addAccount(
|
|
24
|
+
this.pubkey,
|
|
25
|
+
(buffer, slot) => {
|
|
26
|
+
if (!buffer) return;
|
|
27
|
+
|
|
28
|
+
if (this.account && this.account.slot > slot) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const account =
|
|
33
|
+
this.program.account.vaultDepositor.coder.accounts.decode(
|
|
34
|
+
'vaultDepositor',
|
|
35
|
+
buffer
|
|
36
|
+
);
|
|
37
|
+
this.account = { data: account, slot };
|
|
38
|
+
this._eventEmitter.emit('vaultDepositorUpdate', account);
|
|
39
|
+
this._eventEmitter.emit('update');
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
this.errorCallbackId = this.accountLoader.addErrorCallbacks((error) => {
|
|
44
|
+
this._eventEmitter.emit('error', error);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async fetch(): Promise<void> {
|
|
49
|
+
await this.accountLoader.load();
|
|
50
|
+
const bufferAndSlot = this.accountLoader.getBufferAndSlot(this.pubkey);
|
|
51
|
+
if (!bufferAndSlot) return;
|
|
52
|
+
const currentSlot = this.account?.slot ?? 0;
|
|
53
|
+
if (bufferAndSlot.buffer && bufferAndSlot.slot > currentSlot) {
|
|
54
|
+
const account = this.program.account.vaultDepositor.coder.accounts.decode(
|
|
55
|
+
'vaultDepositor',
|
|
56
|
+
bufferAndSlot.buffer
|
|
57
|
+
);
|
|
58
|
+
this.account = { data: account, slot: bufferAndSlot.slot };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
updateData(vaultDepositorAcc: VaultDepositor, slot: number): void {
|
|
63
|
+
if (!this.account || this.account.slot < slot) {
|
|
64
|
+
this.account = { data: vaultDepositorAcc, slot };
|
|
65
|
+
this._eventEmitter.emit('vaultDepositorUpdate', vaultDepositorAcc);
|
|
66
|
+
this._eventEmitter.emit('update');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Vault,
|
|
3
|
+
VaultAccountEvents,
|
|
4
|
+
VaultAccountSubscriber,
|
|
5
|
+
} from '../types/types';
|
|
6
|
+
import { PollingVaultsProgramAccountSubscriber } from './pollingVaultsProgramAccountSubscriber';
|
|
7
|
+
|
|
8
|
+
export class PollingVaultSubscriber
|
|
9
|
+
extends PollingVaultsProgramAccountSubscriber<Vault, VaultAccountEvents>
|
|
10
|
+
implements VaultAccountSubscriber
|
|
11
|
+
{
|
|
12
|
+
async addToAccountLoader(): Promise<void> {
|
|
13
|
+
if (this.callbackId) {
|
|
14
|
+
console.log('Account for vault already added to account loader');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.callbackId = await this.accountLoader.addAccount(
|
|
19
|
+
this.pubkey,
|
|
20
|
+
(buffer, slot) => {
|
|
21
|
+
if (!buffer) return;
|
|
22
|
+
|
|
23
|
+
if (this.account && this.account.slot > slot) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const account = this.program.account.vault.coder.accounts.decode(
|
|
28
|
+
'vault',
|
|
29
|
+
buffer
|
|
30
|
+
);
|
|
31
|
+
this.account = { data: account, slot };
|
|
32
|
+
this._eventEmitter.emit('vaultUpdate', account);
|
|
33
|
+
this._eventEmitter.emit('update');
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
this.errorCallbackId = this.accountLoader.addErrorCallbacks((error) => {
|
|
38
|
+
this._eventEmitter.emit('error', error);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async fetch(): Promise<void> {
|
|
43
|
+
await this.accountLoader.load();
|
|
44
|
+
const bufferAndSlot = this.accountLoader.getBufferAndSlot(this.pubkey);
|
|
45
|
+
if (!bufferAndSlot) return;
|
|
46
|
+
const currentSlot = this.account?.slot ?? 0;
|
|
47
|
+
if (bufferAndSlot.buffer && bufferAndSlot.slot > currentSlot) {
|
|
48
|
+
const account = this.program.account.vault.coder.accounts.decode(
|
|
49
|
+
'vault',
|
|
50
|
+
bufferAndSlot.buffer
|
|
51
|
+
);
|
|
52
|
+
this.account = { data: account, slot: bufferAndSlot.slot };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
updateData(vaultAcc: Vault, slot: number): void {
|
|
57
|
+
if (!this.account || this.account.slot < slot) {
|
|
58
|
+
this.account = { data: vaultAcc, slot };
|
|
59
|
+
this._eventEmitter.emit('vaultUpdate', vaultAcc);
|
|
60
|
+
this._eventEmitter.emit('update');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BulkAccountLoader,
|
|
3
|
+
DataAndSlot,
|
|
4
|
+
NotSubscribedError,
|
|
5
|
+
PublicKey,
|
|
6
|
+
} from '@velocity-exchange/sdk';
|
|
7
|
+
import { Program } from '@coral-xyz/anchor';
|
|
8
|
+
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
9
|
+
import { EventEmitter } from 'events';
|
|
10
|
+
import { DriftVaults } from '../types/drift_vaults';
|
|
11
|
+
import {
|
|
12
|
+
VaultsProgramAccountBaseEvents,
|
|
13
|
+
VaultsProgramAccountSubscriber,
|
|
14
|
+
} from '../types/types';
|
|
15
|
+
|
|
16
|
+
export abstract class PollingVaultsProgramAccountSubscriber<
|
|
17
|
+
Account,
|
|
18
|
+
AccountEvents extends VaultsProgramAccountBaseEvents
|
|
19
|
+
> implements VaultsProgramAccountSubscriber<Account, AccountEvents>
|
|
20
|
+
{
|
|
21
|
+
protected program: Program<DriftVaults>;
|
|
22
|
+
protected _isSubscribed: boolean;
|
|
23
|
+
protected pubkey: PublicKey;
|
|
24
|
+
protected account?: DataAndSlot<Account>;
|
|
25
|
+
|
|
26
|
+
protected _eventEmitter: StrictEventEmitter<EventEmitter, AccountEvents>;
|
|
27
|
+
protected accountLoader: BulkAccountLoader;
|
|
28
|
+
protected callbackId: string | null = null;
|
|
29
|
+
protected errorCallbackId: string | null = null;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
program: Program<DriftVaults>,
|
|
33
|
+
accountPubkey: PublicKey,
|
|
34
|
+
accountLoader: BulkAccountLoader
|
|
35
|
+
) {
|
|
36
|
+
this.accountLoader = accountLoader;
|
|
37
|
+
this._isSubscribed = false;
|
|
38
|
+
this.pubkey = accountPubkey;
|
|
39
|
+
this.program = program;
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
this._eventEmitter = new EventEmitter();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get isSubscribed(): boolean {
|
|
45
|
+
return this._isSubscribed;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get eventEmitter(): StrictEventEmitter<EventEmitter, AccountEvents> {
|
|
49
|
+
return this._eventEmitter;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async subscribe(): Promise<boolean> {
|
|
53
|
+
if (this._isSubscribed) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await this.addToAccountLoader();
|
|
59
|
+
|
|
60
|
+
await this.fetchIfUnloaded();
|
|
61
|
+
if (this.account) {
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
this._eventEmitter.emit('update');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this._isSubscribed = true;
|
|
67
|
+
return true;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error(err);
|
|
70
|
+
this._isSubscribed = false;
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async unsubscribe(): Promise<void> {
|
|
76
|
+
if (!this._isSubscribed) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.accountLoader.removeAccount(this.pubkey, this.callbackId!);
|
|
81
|
+
this.callbackId = null;
|
|
82
|
+
|
|
83
|
+
this.accountLoader.removeErrorCallbacks(this.errorCallbackId!);
|
|
84
|
+
this.errorCallbackId = null;
|
|
85
|
+
|
|
86
|
+
this._isSubscribed = false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async fetchIfUnloaded(): Promise<void> {
|
|
90
|
+
if (this.account === undefined) {
|
|
91
|
+
await this.fetch();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
assertIsSubscribed(): void {
|
|
96
|
+
if (!this._isSubscribed) {
|
|
97
|
+
throw new NotSubscribedError(
|
|
98
|
+
'You must call `subscribe` before using this function'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getAccountAndSlot(): DataAndSlot<Account> {
|
|
104
|
+
this.assertIsSubscribed();
|
|
105
|
+
if (!this.account) {
|
|
106
|
+
throw new Error('Account not loaded');
|
|
107
|
+
}
|
|
108
|
+
return this.account;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
abstract addToAccountLoader(): Promise<void>;
|
|
112
|
+
abstract fetch(): Promise<void>;
|
|
113
|
+
abstract updateData(account: Account, slot: number): void;
|
|
114
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { Program } from '@coral-xyz/anchor';
|
|
2
|
+
import {
|
|
3
|
+
BulkAccountLoader,
|
|
4
|
+
ONE,
|
|
5
|
+
ONE_YEAR,
|
|
6
|
+
PERCENTAGE_PRECISION,
|
|
7
|
+
ZERO,
|
|
8
|
+
BN,
|
|
9
|
+
} from '@velocity-exchange/sdk';
|
|
10
|
+
import { PublicKey } from '@solana/web3.js';
|
|
11
|
+
import { DriftVaults } from '../types/drift_vaults';
|
|
12
|
+
import { Vault, VaultAccountEvents, VaultProtocol } from '../types/types';
|
|
13
|
+
import { PollingVaultSubscriber } from '../accountSubscribers';
|
|
14
|
+
import { VaultsProgramAccount } from './vaultsProgramAccount';
|
|
15
|
+
import { getVaultAddressSync } from '../addresses';
|
|
16
|
+
import { encodeName } from '../name';
|
|
17
|
+
|
|
18
|
+
export class VaultAccount extends VaultsProgramAccount<
|
|
19
|
+
Vault,
|
|
20
|
+
VaultAccountEvents
|
|
21
|
+
> {
|
|
22
|
+
constructor(
|
|
23
|
+
program: Program<DriftVaults>,
|
|
24
|
+
vaultPubkey: PublicKey,
|
|
25
|
+
accountLoader: BulkAccountLoader,
|
|
26
|
+
accountSubscriptionType: 'polling' | 'websocket' = 'polling'
|
|
27
|
+
) {
|
|
28
|
+
super();
|
|
29
|
+
|
|
30
|
+
if (accountSubscriptionType === 'polling') {
|
|
31
|
+
this.accountSubscriber = new PollingVaultSubscriber(
|
|
32
|
+
program,
|
|
33
|
+
vaultPubkey,
|
|
34
|
+
accountLoader
|
|
35
|
+
);
|
|
36
|
+
} else {
|
|
37
|
+
throw new Error('Websocket subscription not yet implemented');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static getAddressSync(programId: PublicKey, vaultName: string): PublicKey {
|
|
42
|
+
return getVaultAddressSync(programId, encodeName(vaultName));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Calculates the new total shares and management fee shares after a management fee is applied.
|
|
47
|
+
* Only applies to deposits.
|
|
48
|
+
* Management fee is applied to a depositor's existing equity, and the total shares are updated (increased) accordingly.
|
|
49
|
+
* @param vaultEquity - The equity of the vault.
|
|
50
|
+
* @returns An object containing the new total shares and management fee shares.
|
|
51
|
+
*/
|
|
52
|
+
calcSharesAfterManagementFee(vaultEquity: BN): {
|
|
53
|
+
totalShares: BN;
|
|
54
|
+
managementFeeShares: BN;
|
|
55
|
+
} {
|
|
56
|
+
const accountData = this.accountSubscriber.getAccountAndSlot().data;
|
|
57
|
+
|
|
58
|
+
const depositorsEquity = accountData.userShares
|
|
59
|
+
.mul(vaultEquity)
|
|
60
|
+
.div(accountData.totalShares);
|
|
61
|
+
|
|
62
|
+
if (accountData.managementFee.eq(ZERO) || depositorsEquity.lte(ZERO)) {
|
|
63
|
+
return {
|
|
64
|
+
totalShares: accountData.totalShares,
|
|
65
|
+
managementFeeShares: ZERO,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const now = new BN(Date.now() / 1000);
|
|
69
|
+
const sinceLast = now.sub(accountData.lastFeeUpdateTs);
|
|
70
|
+
|
|
71
|
+
let managementFeeAmount = depositorsEquity
|
|
72
|
+
.mul(accountData.managementFee)
|
|
73
|
+
.div(PERCENTAGE_PRECISION)
|
|
74
|
+
.mul(sinceLast)
|
|
75
|
+
.div(ONE_YEAR);
|
|
76
|
+
managementFeeAmount = BN.min(
|
|
77
|
+
managementFeeAmount,
|
|
78
|
+
depositorsEquity.sub(ONE)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const newTotalSharesFactor = depositorsEquity
|
|
82
|
+
.mul(PERCENTAGE_PRECISION)
|
|
83
|
+
.div(depositorsEquity.sub(managementFeeAmount));
|
|
84
|
+
let newTotalShares = accountData.totalShares
|
|
85
|
+
.mul(newTotalSharesFactor)
|
|
86
|
+
.div(PERCENTAGE_PRECISION);
|
|
87
|
+
newTotalShares = BN.max(newTotalShares, accountData.userShares);
|
|
88
|
+
|
|
89
|
+
const managementFeeShares = newTotalShares.sub(accountData.totalShares);
|
|
90
|
+
|
|
91
|
+
return { totalShares: newTotalShares, managementFeeShares };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
calcSharesAfterManagementAndProtocolFee(
|
|
95
|
+
vaultEquity: BN,
|
|
96
|
+
vaultProtocol: VaultProtocol
|
|
97
|
+
): {
|
|
98
|
+
totalShares: BN;
|
|
99
|
+
managementFeeShares: BN;
|
|
100
|
+
protocolFeeShares: BN;
|
|
101
|
+
} {
|
|
102
|
+
const accountData = this.accountSubscriber.getAccountAndSlot().data;
|
|
103
|
+
|
|
104
|
+
if (!accountData.vaultProtocol) {
|
|
105
|
+
throw new Error('VaultProtocol does not exist for vault');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const depositorsEquity = accountData.userShares
|
|
109
|
+
.mul(vaultEquity)
|
|
110
|
+
.div(accountData.totalShares);
|
|
111
|
+
|
|
112
|
+
const now = new BN(Date.now() / 1000);
|
|
113
|
+
const sinceLast = now.sub(accountData.lastFeeUpdateTs);
|
|
114
|
+
|
|
115
|
+
if (
|
|
116
|
+
!accountData.managementFee.eq(ZERO) &&
|
|
117
|
+
!vaultProtocol.protocolFee.eq(ZERO) &&
|
|
118
|
+
depositorsEquity.gt(ZERO)
|
|
119
|
+
) {
|
|
120
|
+
const totalFee = accountData.managementFee.add(vaultProtocol.protocolFee);
|
|
121
|
+
const totalFeePayment = depositorsEquity
|
|
122
|
+
.mul(totalFee)
|
|
123
|
+
.div(PERCENTAGE_PRECISION)
|
|
124
|
+
.mul(sinceLast)
|
|
125
|
+
.div(ONE_YEAR);
|
|
126
|
+
const managementFeePayment = depositorsEquity
|
|
127
|
+
.mul(accountData.managementFee)
|
|
128
|
+
.div(PERCENTAGE_PRECISION)
|
|
129
|
+
.mul(sinceLast)
|
|
130
|
+
.div(ONE_YEAR);
|
|
131
|
+
const protocolFeePayment = BN.min(
|
|
132
|
+
totalFeePayment,
|
|
133
|
+
depositorsEquity.sub(new BN(1))
|
|
134
|
+
)
|
|
135
|
+
.mul(vaultProtocol.protocolFee)
|
|
136
|
+
.div(totalFee);
|
|
137
|
+
|
|
138
|
+
const newTotalSharesFactor = depositorsEquity
|
|
139
|
+
.mul(PERCENTAGE_PRECISION)
|
|
140
|
+
.div(
|
|
141
|
+
depositorsEquity.sub(managementFeePayment).sub(protocolFeePayment)
|
|
142
|
+
);
|
|
143
|
+
const newTotalShares = BN.max(
|
|
144
|
+
accountData.totalShares
|
|
145
|
+
.mul(newTotalSharesFactor)
|
|
146
|
+
.div(PERCENTAGE_PRECISION),
|
|
147
|
+
accountData.userShares
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
(managementFeePayment.eq(ZERO) && protocolFeePayment.eq(ZERO)) ||
|
|
152
|
+
accountData.totalShares.eq(newTotalShares)
|
|
153
|
+
) {
|
|
154
|
+
return {
|
|
155
|
+
totalShares: accountData.totalShares,
|
|
156
|
+
managementFeeShares: ZERO,
|
|
157
|
+
protocolFeeShares: ZERO,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const managementFeeShares = newTotalShares.sub(accountData.totalShares);
|
|
162
|
+
const protocolFeeShares = newTotalShares.sub(accountData.totalShares);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
totalShares: newTotalShares,
|
|
166
|
+
managementFeeShares,
|
|
167
|
+
protocolFeeShares,
|
|
168
|
+
};
|
|
169
|
+
} else if (
|
|
170
|
+
accountData.managementFee.eq(ZERO) &&
|
|
171
|
+
!vaultProtocol.protocolFee.eq(ZERO) &&
|
|
172
|
+
depositorsEquity.gt(ZERO)
|
|
173
|
+
) {
|
|
174
|
+
const protocolFeePayment = depositorsEquity
|
|
175
|
+
.mul(vaultProtocol.protocolFee)
|
|
176
|
+
.div(PERCENTAGE_PRECISION)
|
|
177
|
+
.mul(sinceLast)
|
|
178
|
+
.div(ONE_YEAR);
|
|
179
|
+
|
|
180
|
+
const newTotalSharesFactor = depositorsEquity
|
|
181
|
+
.mul(PERCENTAGE_PRECISION)
|
|
182
|
+
.div(depositorsEquity.sub(protocolFeePayment));
|
|
183
|
+
const newTotalShares = BN.max(
|
|
184
|
+
accountData.totalShares
|
|
185
|
+
.mul(newTotalSharesFactor)
|
|
186
|
+
.div(PERCENTAGE_PRECISION),
|
|
187
|
+
accountData.userShares
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
protocolFeePayment.eq(ZERO) ||
|
|
192
|
+
accountData.totalShares.eq(newTotalShares)
|
|
193
|
+
) {
|
|
194
|
+
return {
|
|
195
|
+
totalShares: accountData.totalShares,
|
|
196
|
+
managementFeeShares: ZERO,
|
|
197
|
+
protocolFeeShares: ZERO,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const protocolFeeShares = newTotalShares.sub(accountData.totalShares);
|
|
202
|
+
return {
|
|
203
|
+
totalShares: newTotalShares,
|
|
204
|
+
managementFeeShares: ZERO,
|
|
205
|
+
protocolFeeShares,
|
|
206
|
+
};
|
|
207
|
+
} else if (
|
|
208
|
+
!accountData.managementFee.eq(ZERO) &&
|
|
209
|
+
vaultProtocol.protocolFee.eq(ZERO) &&
|
|
210
|
+
depositorsEquity.gt(ZERO)
|
|
211
|
+
) {
|
|
212
|
+
const managementFeePayment = BN.min(
|
|
213
|
+
depositorsEquity
|
|
214
|
+
.mul(accountData.managementFee)
|
|
215
|
+
.div(PERCENTAGE_PRECISION)
|
|
216
|
+
.mul(sinceLast)
|
|
217
|
+
.div(ONE_YEAR),
|
|
218
|
+
depositorsEquity.sub(ONE)
|
|
219
|
+
);
|
|
220
|
+
const newTotalSharesFactor = depositorsEquity
|
|
221
|
+
.mul(PERCENTAGE_PRECISION)
|
|
222
|
+
.div(depositorsEquity.sub(managementFeePayment));
|
|
223
|
+
const newTotalShares = BN.max(
|
|
224
|
+
accountData.totalShares
|
|
225
|
+
.mul(newTotalSharesFactor)
|
|
226
|
+
.div(PERCENTAGE_PRECISION),
|
|
227
|
+
accountData.userShares
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (
|
|
231
|
+
managementFeePayment.eq(ZERO) ||
|
|
232
|
+
accountData.totalShares.eq(newTotalShares)
|
|
233
|
+
) {
|
|
234
|
+
return {
|
|
235
|
+
totalShares: accountData.totalShares,
|
|
236
|
+
managementFeeShares: ZERO,
|
|
237
|
+
protocolFeeShares: ZERO,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const managementFeeShares = newTotalShares.sub(accountData.totalShares);
|
|
242
|
+
return {
|
|
243
|
+
totalShares: newTotalShares,
|
|
244
|
+
managementFeeShares,
|
|
245
|
+
protocolFeeShares: ZERO,
|
|
246
|
+
};
|
|
247
|
+
} else {
|
|
248
|
+
return {
|
|
249
|
+
totalShares: accountData.totalShares,
|
|
250
|
+
managementFeeShares: ZERO,
|
|
251
|
+
protocolFeeShares: ZERO,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Program } from '@coral-xyz/anchor';
|
|
2
|
+
import {
|
|
3
|
+
BN,
|
|
4
|
+
BulkAccountLoader,
|
|
5
|
+
PERCENTAGE_PRECISION,
|
|
6
|
+
ZERO,
|
|
7
|
+
} from '@velocity-exchange/sdk';
|
|
8
|
+
import { PublicKey } from '@solana/web3.js';
|
|
9
|
+
import { DriftVaults } from '../types/drift_vaults';
|
|
10
|
+
import { VaultDepositor, VaultDepositorAccountEvents } from '../types/types';
|
|
11
|
+
import { PollingVaultDepositorSubscriber } from '../accountSubscribers';
|
|
12
|
+
import { VaultsProgramAccount } from './vaultsProgramAccount';
|
|
13
|
+
import { getVaultDepositorAddressSync } from '../addresses';
|
|
14
|
+
|
|
15
|
+
export class VaultDepositorAccount extends VaultsProgramAccount<
|
|
16
|
+
VaultDepositor,
|
|
17
|
+
VaultDepositorAccountEvents
|
|
18
|
+
> {
|
|
19
|
+
constructor(
|
|
20
|
+
program: Program<DriftVaults>,
|
|
21
|
+
vaultDepositorPubkey: PublicKey,
|
|
22
|
+
accountLoader: BulkAccountLoader,
|
|
23
|
+
accountSubscriptionType: 'polling' | 'websocket' = 'polling'
|
|
24
|
+
) {
|
|
25
|
+
super();
|
|
26
|
+
|
|
27
|
+
if (accountSubscriptionType === 'polling') {
|
|
28
|
+
this.accountSubscriber = new PollingVaultDepositorSubscriber(
|
|
29
|
+
program,
|
|
30
|
+
vaultDepositorPubkey,
|
|
31
|
+
accountLoader
|
|
32
|
+
);
|
|
33
|
+
} else {
|
|
34
|
+
throw new Error('Websocket subscription not yet implemented');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static getAddressSync(
|
|
39
|
+
programId: PublicKey,
|
|
40
|
+
vault: PublicKey,
|
|
41
|
+
authority: PublicKey
|
|
42
|
+
): PublicKey {
|
|
43
|
+
return getVaultDepositorAddressSync(programId, vault, authority);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Calculates the percentage of a depositor's equity that will be paid as profit share fees.
|
|
48
|
+
*
|
|
49
|
+
* @param vaultProfitShare Vault's profit share fee
|
|
50
|
+
* @param depositorEquity Vault depositor's equity amount
|
|
51
|
+
*/
|
|
52
|
+
calcProfitShareFeesPct(vaultProfitShare: BN, depositorEquity: BN): BN {
|
|
53
|
+
const accountData = this.accountSubscriber.getAccountAndSlot().data;
|
|
54
|
+
|
|
55
|
+
const profit = depositorEquity
|
|
56
|
+
.sub(accountData.netDeposits)
|
|
57
|
+
.sub(accountData.cumulativeProfitShareAmount);
|
|
58
|
+
|
|
59
|
+
if (profit.lte(new BN(0)) || depositorEquity.isZero()) {
|
|
60
|
+
return ZERO;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const profitShareAmount = profit
|
|
64
|
+
.mul(vaultProfitShare)
|
|
65
|
+
.div(PERCENTAGE_PRECISION);
|
|
66
|
+
|
|
67
|
+
if (depositorEquity.eq(new BN(0))) {
|
|
68
|
+
return ZERO;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const profitShareProportion = profitShareAmount
|
|
72
|
+
.mul(PERCENTAGE_PRECISION)
|
|
73
|
+
.div(depositorEquity);
|
|
74
|
+
|
|
75
|
+
return profitShareProportion;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import EventEmitter from 'events';
|
|
2
|
+
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
3
|
+
import {
|
|
4
|
+
VaultsProgramAccountBaseEvents,
|
|
5
|
+
VaultsProgramAccountSubscriber,
|
|
6
|
+
} from '../types/types';
|
|
7
|
+
|
|
8
|
+
export abstract class VaultsProgramAccount<
|
|
9
|
+
Account,
|
|
10
|
+
AccountEvents extends VaultsProgramAccountBaseEvents
|
|
11
|
+
> {
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
accountSubscriber: VaultsProgramAccountSubscriber<Account, AccountEvents>;
|
|
14
|
+
|
|
15
|
+
get isSubscribed(): boolean {
|
|
16
|
+
return this.accountSubscriber.isSubscribed;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get eventEmitter(): StrictEventEmitter<EventEmitter, AccountEvents> {
|
|
20
|
+
return this.accountSubscriber.eventEmitter;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async subscribe(): Promise<boolean> {
|
|
24
|
+
return await this.accountSubscriber.subscribe();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async unsubscribe(): Promise<void> {
|
|
28
|
+
return await this.accountSubscriber.unsubscribe();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getData(): Account {
|
|
32
|
+
return this.accountSubscriber.getAccountAndSlot()?.data;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async updateData(newData: Account, slot: number): Promise<void> {
|
|
36
|
+
return await this.accountSubscriber.updateData(newData, slot);
|
|
37
|
+
}
|
|
38
|
+
}
|