Skip to content

Commit f99527a

Browse files
committed
refactor(sdk): add OneShotUserStatsAccountSubscriber
1 parent 31c69e0 commit f99527a

File tree

7 files changed

+155
-3
lines changed

7 files changed

+155
-3
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {
2+
DataAndSlot,
3+
UserStatsAccountEvents,
4+
UserStatsAccountSubscriber,
5+
} from './types';
6+
import { PublicKey } from '@solana/web3.js';
7+
import StrictEventEmitter from 'strict-event-emitter-types';
8+
import { EventEmitter } from 'events';
9+
import { UserStatsAccount } from '../types';
10+
11+
/**
12+
* Basic implementation of UserStatsAccountSubscriber. It will only take in UserStatsAccount
13+
* data during initialization and will not fetch or subscribe to updates.
14+
*/
15+
export class BasicUserStatsAccountSubscriber
16+
implements UserStatsAccountSubscriber
17+
{
18+
isSubscribed: boolean;
19+
eventEmitter: StrictEventEmitter<EventEmitter, UserStatsAccountEvents>;
20+
userStatsAccountPublicKey: PublicKey;
21+
22+
callbackId?: string;
23+
errorCallbackId?: string;
24+
25+
userStats: DataAndSlot<UserStatsAccount>;
26+
27+
public constructor(
28+
userStatsAccountPublicKey: PublicKey,
29+
data?: UserStatsAccount,
30+
slot?: number
31+
) {
32+
this.isSubscribed = true;
33+
this.eventEmitter = new EventEmitter();
34+
this.userStatsAccountPublicKey = userStatsAccountPublicKey;
35+
this.userStats = { data, slot };
36+
}
37+
38+
async subscribe(_userStatsAccount?: UserStatsAccount): Promise<boolean> {
39+
return true;
40+
}
41+
42+
async addToAccountLoader(): Promise<void> {}
43+
44+
async fetch(): Promise<void> {}
45+
46+
doesAccountExist(): boolean {
47+
return this.userStats !== undefined;
48+
}
49+
50+
async unsubscribe(): Promise<void> {}
51+
52+
assertIsSubscribed(): void {}
53+
54+
public getUserStatsAccountAndSlot(): DataAndSlot<UserStatsAccount> {
55+
return this.userStats;
56+
}
57+
58+
public updateData(userStatsAccount: UserStatsAccount, slot: number): void {
59+
if (!this.userStats || slot >= (this.userStats.slot ?? 0)) {
60+
this.userStats = { data: userStatsAccount, slot };
61+
this.eventEmitter.emit('userStatsAccountUpdate', userStatsAccount);
62+
this.eventEmitter.emit('update');
63+
}
64+
}
65+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Commitment, PublicKey } from '@solana/web3.js';
2+
import { UserStatsAccount } from '../types';
3+
import { BasicUserStatsAccountSubscriber } from './basicUserStatsAccountSubscriber';
4+
import { Program } from '@coral-xyz/anchor';
5+
import { UserStatsAccountSubscriber } from './types';
6+
7+
/**
8+
* Simple implementation of UserStatsAccountSubscriber. It will fetch the UserStatsAccount
9+
* data on subscribe (or call to fetch) if no account data is provided on init.
10+
* Expect to use only 1 RPC call unless you call fetch repeatedly.
11+
*/
12+
export class OneShotUserStatsAccountSubscriber
13+
extends BasicUserStatsAccountSubscriber
14+
implements UserStatsAccountSubscriber
15+
{
16+
program: Program;
17+
commitment: Commitment;
18+
19+
public constructor(
20+
program: Program,
21+
userStatsAccountPublicKey: PublicKey,
22+
data?: UserStatsAccount,
23+
slot?: number,
24+
commitment?: Commitment
25+
) {
26+
super(userStatsAccountPublicKey, data, slot);
27+
this.program = program;
28+
this.commitment = commitment ?? 'confirmed';
29+
}
30+
31+
async subscribe(userStatsAccount?: UserStatsAccount): Promise<boolean> {
32+
if (userStatsAccount) {
33+
this.userStats = { data: userStatsAccount, slot: this.userStats.slot };
34+
return true;
35+
}
36+
37+
await this.fetchIfUnloaded();
38+
if (this.doesAccountExist()) {
39+
this.eventEmitter.emit('update');
40+
}
41+
return true;
42+
}
43+
44+
async fetchIfUnloaded(): Promise<void> {
45+
if (this.userStats.data === undefined) {
46+
await this.fetch();
47+
}
48+
}
49+
50+
async fetch(): Promise<void> {
51+
try {
52+
const dataAndContext =
53+
await this.program.account.userStats.fetchAndContext(
54+
this.userStatsAccountPublicKey,
55+
this.commitment
56+
);
57+
if (dataAndContext.context.slot > (this.userStats?.slot ?? 0)) {
58+
this.userStats = {
59+
data: dataAndContext.data as UserStatsAccount,
60+
slot: dataAndContext.context.slot,
61+
};
62+
}
63+
} catch (e) {
64+
console.error(
65+
`OneShotUserStatsAccountSubscriber.fetch() UserStatsAccount does not exist: ${e.message}`
66+
);
67+
}
68+
}
69+
}

sdk/src/driftClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ export class DriftClient {
924924
this.userStats = new UserStats({
925925
driftClient: this,
926926
userStatsAccountPublicKey: this.userStatsAccountPublicKey,
927-
accountSubscription: this.userAccountSubscriptionConfig,
927+
accountSubscription: this.userStatsAccountSubscriptionConfig,
928928
});
929929

930930
this.userStats.subscribe();

sdk/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export * from './accounts/pollingInsuranceFundStakeAccountSubscriber';
2929
export * from './accounts/pollingHighLeverageModeConfigAccountSubscriber';
3030
export * from './accounts/basicUserAccountSubscriber';
3131
export * from './accounts/oneShotUserAccountSubscriber';
32+
export * from './accounts/oneShotUserStatsAccountSubscriber';
3233
export * from './accounts/types';
3334
export * from './addresses/pda';
3435
export * from './adminClient';

sdk/src/userStats.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,14 @@ export class UserStats {
5454
},
5555
config.accountSubscription.commitment
5656
);
57+
} else if (config.accountSubscription?.type === 'custom') {
58+
this.accountSubscriber =
59+
config.accountSubscription.userStatsAccountSubscriber;
5760
} else {
61+
const exhaustiveCheck: never = config.accountSubscription;
62+
5863
throw new Error(
59-
`Unknown user stats account subscription type: ${config.accountSubscription?.type}`
64+
`Unknown user stats account subscription type: ${exhaustiveCheck}`
6065
);
6166
}
6267
}

sdk/src/userStatsConfig.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DriftClient } from './driftClient';
22
import { Commitment, PublicKey } from '@solana/web3.js';
33
import { BulkAccountLoader } from './accounts/bulkAccountLoader';
4-
import { GrpcConfigs } from './accounts/types';
4+
import { GrpcConfigs, UserStatsAccountSubscriber } from './accounts/types';
55

66
export type UserStatsConfig = {
77
accountSubscription?: UserStatsSubscriptionConfig;
@@ -22,6 +22,7 @@ export type UserStatsSubscriptionConfig =
2222
}
2323
| {
2424
type: 'custom';
25+
userStatsAccountSubscriber: UserStatsAccountSubscriber;
2526
}
2627
| {
2728
type: 'grpc';

sdk/src/wallet.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
VersionedTransaction,
66
} from '@solana/web3.js';
77
import { IWallet, IVersionedWallet } from './types';
8+
import nacl from 'tweetnacl';
89

910
export class Wallet implements IWallet, IVersionedWallet {
1011
constructor(readonly payer: Keypair) {}
@@ -41,3 +42,13 @@ export class Wallet implements IWallet, IVersionedWallet {
4142
return this.payer.publicKey;
4243
}
4344
}
45+
46+
export class WalletV2 extends Wallet {
47+
constructor(readonly payer: Keypair) {
48+
super(payer);
49+
}
50+
51+
async signMessage(message: Uint8Array): Promise<Uint8Array> {
52+
return Buffer.from(nacl.sign.detached(message, this.payer.secretKey));
53+
}
54+
}

0 commit comments

Comments
 (0)