-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sdk: allow delegates in single client #432
Changes from 5 commits
4c02ea4
978fd42
2406bad
bd70516
8ef24c1
7a61cc0
25438f3
96ec680
b19cdd1
e931f2e
bf06214
615d220
24d651b
aee3d1b
1200b45
a706d73
68c1ac1
791f2d4
91c4bc6
9d74c0c
dca79b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -117,9 +117,10 @@ export class DriftClient { | |
public program: Program; | ||
provider: AnchorProvider; | ||
opts?: ConfirmOptions; | ||
users = new Map<number, User>(); | ||
users = new Map<string, User>(); | ||
userStats?: UserStats; | ||
activeSubAccountId: number; | ||
activeAuthority: PublicKey; | ||
userAccountSubscriptionConfig: UserSubscriptionConfig; | ||
accountSubscriber: DriftClientAccountSubscriber; | ||
eventEmitter: StrictEventEmitter<EventEmitter, DriftClientAccountEvents>; | ||
|
@@ -154,8 +155,8 @@ export class DriftClient { | |
); | ||
|
||
this.authority = config.authority ?? this.wallet.publicKey; | ||
const subAccountIds = config.subAccountIds ?? [0]; | ||
this.activeSubAccountId = config.activeSubAccountId ?? subAccountIds[0]; | ||
this.activeSubAccountId = config.activeSubAccountId; | ||
this.activeAuthority = this.authority; | ||
this.userAccountSubscriptionConfig = | ||
config.accountSubscription?.type === 'polling' | ||
? { | ||
|
@@ -165,7 +166,14 @@ export class DriftClient { | |
: { | ||
type: 'websocket', | ||
}; | ||
this.createUsers(subAccountIds, this.userAccountSubscriptionConfig); | ||
|
||
// load all or some users and add them to the driftClient | ||
this.addAllUsers( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i dont think we want to do any async calls in the constructor, thats why the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we have a new DriftClientConfig Param which is |
||
config.subAccountIds, | ||
!this.activeSubAccountId, | ||
config.includeDelegates | ||
); | ||
|
||
if (config.userStats) { | ||
this.userStats = new UserStats({ | ||
driftClient: this, | ||
|
@@ -227,23 +235,18 @@ export class DriftClient { | |
); | ||
} | ||
|
||
createUsers( | ||
subAccountIds: number[], | ||
accountSubscriptionConfig: UserSubscriptionConfig | ||
): void { | ||
for (const subAccountId of subAccountIds) { | ||
const user = this.createUser(subAccountId, accountSubscriptionConfig); | ||
this.users.set(subAccountId, user); | ||
} | ||
public getUserMapKey(subAccountId: number, authority: PublicKey): string { | ||
return `${subAccountId}_${authority.toString()}`; | ||
} | ||
|
||
createUser( | ||
subAccountId: number, | ||
accountSubscriptionConfig: UserSubscriptionConfig | ||
accountSubscriptionConfig: UserSubscriptionConfig, | ||
authority?: PublicKey | ||
): User { | ||
const userAccountPublicKey = getUserAccountPublicKeySync( | ||
this.program.programId, | ||
this.authority, | ||
authority ?? this.authority, | ||
subAccountId | ||
); | ||
|
||
|
@@ -427,11 +430,13 @@ export class DriftClient { | |
* @param newWallet | ||
* @param subAccountIds | ||
* @param activeSubAccountId | ||
* @param includeDelegates | ||
*/ | ||
public async updateWallet( | ||
newWallet: IWallet, | ||
subAccountIds = [0], | ||
activeSubAccountId = 0 | ||
subAccountIds?: number[], | ||
activeSubAccountId?: number, | ||
includeDelegates?: boolean | ||
): Promise<void> { | ||
const newProvider = new AnchorProvider( | ||
this.connection, | ||
|
@@ -446,11 +451,19 @@ export class DriftClient { | |
|
||
// Update provider for txSender with new wallet details | ||
this.txSender.provider = newProvider; | ||
|
||
this.wallet = newWallet; | ||
this.provider = newProvider; | ||
this.program = newProgram; | ||
this.authority = newWallet.publicKey; | ||
this.activeSubAccountId = activeSubAccountId; | ||
this.userStatsAccountPublicKey = undefined; | ||
|
||
this.users.clear(); | ||
await this.addAllUsers( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are you adding the users before the unsubscribe? feel like this is backwards? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mistake, not sure how it wasn't breaking anything |
||
subAccountIds, | ||
!this.activeSubAccountId, | ||
includeDelegates | ||
); | ||
|
||
if (this.isSubscribed) { | ||
await Promise.all(this.unsubscribeUsers()); | ||
|
@@ -460,49 +473,71 @@ export class DriftClient { | |
|
||
this.userStats = new UserStats({ | ||
driftClient: this, | ||
userStatsAccountPublicKey: getUserStatsAccountPublicKey( | ||
this.program.programId, | ||
this.authority | ||
), | ||
userStatsAccountPublicKey: this.getUserStatsAccountPublicKey(), | ||
accountSubscription: this.userAccountSubscriptionConfig, | ||
}); | ||
} | ||
} | ||
this.users.clear(); | ||
this.createUsers(subAccountIds, this.userAccountSubscriptionConfig); | ||
if (this.isSubscribed) { | ||
await Promise.all(this.subscribeUsers()); | ||
|
||
if (this.userStats) { | ||
await this.userStats.subscribe(); | ||
} | ||
} | ||
|
||
this.activeSubAccountId = activeSubAccountId; | ||
this.userStatsAccountPublicKey = undefined; | ||
} | ||
|
||
public switchActiveUser(subAccountId: number) { | ||
public switchActiveUser(subAccountId: number, authority?: PublicKey) { | ||
this.activeSubAccountId = subAccountId; | ||
this.activeAuthority = authority ?? this.authority; | ||
} | ||
|
||
public async addUser(subAccountId: number): Promise<void> { | ||
if (this.users.has(subAccountId)) { | ||
public async addUser( | ||
subAccountId: number, | ||
authority?: PublicKey | ||
): Promise<void> { | ||
authority = authority ?? this.authority; | ||
const userKey = this.getUserMapKey(subAccountId, authority); | ||
|
||
if (this.users.has(userKey)) { | ||
return; | ||
} | ||
|
||
const user = this.createUser( | ||
subAccountId, | ||
this.userAccountSubscriptionConfig | ||
this.userAccountSubscriptionConfig, | ||
authority | ||
); | ||
await user.subscribe(); | ||
this.users.set(subAccountId, user); | ||
this.users.set(userKey, user); | ||
} | ||
|
||
public async addAllUsers(): Promise<void> { | ||
const userAccounts = await this.getUserAccountsForAuthority(this.authority); | ||
public async addAllUsers( | ||
subAccountIds?: number[], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this just for the constructor? would be nice if we didn't need to add this as it'd make the function less confusing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's to allow them to only subscribe to a subset of subaccount ids |
||
setFirstActive?: boolean, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when do you set this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. at the moment if the user passes a subaccount id to the constructor or updateWallet it defaults the user to that, if not then this flag is true and it defaults to the first user There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i feel like the constructor always needs to set the activeSubAccount for backwards compatability |
||
includeDelegates?: boolean | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. couldn't you just check that if the this.authority == wallet.pubkey to decide if the user has delegate set? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure what you mean by this, this flag indicates we want to load any/all of the accounts delegated to an authority as part of their subscribed driftclient users There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah i don't think my comment made sense, nvm |
||
): Promise<void> { | ||
let userAccounts = | ||
(await this.getUserAccountsForAuthority(this.authority)) ?? []; | ||
if (subAccountIds) { | ||
userAccounts = userAccounts.filter((userAccount) => | ||
subAccountIds.includes(userAccount.subAccountId) | ||
); | ||
} | ||
|
||
for (const userAccount of userAccounts) { | ||
await this.addUser(userAccount.subAccountId); | ||
this.addUser(userAccount.subAccountId, this.authority); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. didn't have any race condition issues with it and tested a lot, wanted to make it faster, will add back |
||
} | ||
|
||
let delegatedAccounts = []; | ||
if (includeDelegates) { | ||
delegatedAccounts = await this.getUserAccountsForDelegate(this.authority); | ||
for (const delegatedAccount of delegatedAccounts) { | ||
this.addUser(delegatedAccount.subAccountId, delegatedAccount.authority); | ||
} | ||
} | ||
|
||
if (setFirstActive) { | ||
const firstUser = userAccounts[0] ?? delegatedAccounts[0]; | ||
this.switchActiveUser( | ||
firstUser?.subAccountId ?? 0, | ||
firstUser?.authority ?? this.authority | ||
); | ||
} | ||
} | ||
|
||
|
@@ -689,7 +724,7 @@ export class DriftClient { | |
subAccountId | ||
); | ||
|
||
await this.addUser(subAccountId); | ||
await this.addUser(subAccountId, this.wallet.publicKey); | ||
const remainingAccounts = this.getRemainingAccounts({ | ||
userAccounts: [this.getUserAccount(subAccountId)], | ||
}); | ||
|
@@ -761,9 +796,9 @@ export class DriftClient { | |
}, | ||
]); | ||
|
||
return programAccounts.map( | ||
(programAccount) => programAccount.account as UserAccount | ||
); | ||
return programAccounts | ||
.map((programAccount) => programAccount.account as UserAccount) | ||
.sort((a, b) => a.subAccountId - b.subAccountId); | ||
} | ||
|
||
public async getUserAccountsAndAddressesForAuthority( | ||
|
@@ -797,9 +832,9 @@ export class DriftClient { | |
}, | ||
]); | ||
|
||
return programAccounts.map( | ||
(programAccount) => programAccount.account as UserAccount | ||
); | ||
return programAccounts | ||
.map((programAccount) => programAccount.account as UserAccount) | ||
.sort((a, b) => a.subAccountId - b.subAccountId); | ||
} | ||
|
||
public async getReferredUserStatsAccountsByReferrer( | ||
|
@@ -863,18 +898,22 @@ export class DriftClient { | |
this.opts | ||
); | ||
|
||
await this.users.get(subAccountId)?.unsubscribe(); | ||
this.users.delete(subAccountId); | ||
const userMapKey = this.getUserMapKey(subAccountId, this.wallet.publicKey); | ||
await this.users.get(userMapKey)?.unsubscribe(); | ||
this.users.delete(userMapKey); | ||
|
||
return txSig; | ||
} | ||
|
||
public getUser(subAccountId?: number): User { | ||
public getUser(subAccountId?: number, authority?: PublicKey): User { | ||
subAccountId = subAccountId ?? this.activeSubAccountId; | ||
if (!this.users.has(subAccountId)) { | ||
authority = authority ?? this.activeAuthority; | ||
const userMapKey = this.getUserMapKey(subAccountId, authority); | ||
|
||
if (!this.users.has(userMapKey)) { | ||
throw new Error(`Clearing House has no user for user id ${subAccountId}`); | ||
} | ||
return this.users.get(subAccountId); | ||
return this.users.get(userMapKey); | ||
} | ||
|
||
public getUsers(): User[] { | ||
|
@@ -911,12 +950,18 @@ export class DriftClient { | |
return this.userStatsAccountPublicKey; | ||
} | ||
|
||
public async getUserAccountPublicKey(): Promise<PublicKey> { | ||
return this.getUser().userAccountPublicKey; | ||
public async getUserAccountPublicKey( | ||
subAccountId?: number, | ||
authority?: PublicKey | ||
): Promise<PublicKey> { | ||
return this.getUser(subAccountId, authority).userAccountPublicKey; | ||
} | ||
|
||
public getUserAccount(subAccountId?: number): UserAccount | undefined { | ||
return this.getUser(subAccountId).getUserAccount(); | ||
public getUserAccount( | ||
subAccountId?: number, | ||
authority?: PublicKey | ||
): UserAccount | undefined { | ||
return this.getUser(subAccountId, authority).getUserAccount(); | ||
} | ||
|
||
/** | ||
|
@@ -1777,9 +1822,14 @@ export class DriftClient { | |
); | ||
|
||
let remainingAccounts; | ||
if (this.users.has(fromSubAccountId)) { | ||
|
||
const userMapKey = this.getUserMapKey( | ||
fromSubAccountId, | ||
this.wallet.publicKey | ||
); | ||
if (this.users.has(userMapKey)) { | ||
remainingAccounts = this.getRemainingAccounts({ | ||
userAccounts: [this.users.get(fromSubAccountId).getUserAccount()], | ||
userAccounts: [this.users.get(userMapKey).getUserAccount()], | ||
useMarketLastSlotCache: true, | ||
writableSpotMarketIndexes: [marketIndex], | ||
}); | ||
|
@@ -2109,8 +2159,8 @@ export class DriftClient { | |
marketOrderTx.recentBlockhash = currentBlockHash; | ||
fillTx.recentBlockhash = currentBlockHash; | ||
|
||
marketOrderTx.feePayer = userAccount.authority; | ||
fillTx.feePayer = userAccount.authority; | ||
marketOrderTx.feePayer = this.authority; | ||
fillTx.feePayer = this.authority; | ||
|
||
const [signedMarketOrderTx, signedFillTx] = | ||
await this.provider.wallet.signAllTransactions([marketOrderTx, fillTx]); | ||
|
@@ -2897,7 +2947,7 @@ export class DriftClient { | |
user: UserAccount, | ||
txParams?: TxParams | ||
): Promise<TransactionSignature> { | ||
const { txSig } = await this.txSender.send( | ||
const { txSig } = await this.sendTransaction( | ||
wrapInTx( | ||
await this.getForceCancelOrdersIx(userAccountPublicKey, user), | ||
txParams?.computeUnits, | ||
|
@@ -2936,7 +2986,7 @@ export class DriftClient { | |
user: UserAccount, | ||
txParams?: TxParams | ||
): Promise<TransactionSignature> { | ||
const { txSig } = await this.txSender.send( | ||
const { txSig } = await this.sendTransaction( | ||
wrapInTx( | ||
await this.getUpdateUserIdleIx(userAccountPublicKey, user), | ||
txParams?.computeUnits, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,7 +56,7 @@ export class RetryTxSender implements TxSender { | |
opts = this.provider.opts; | ||
} | ||
|
||
const signedTx = preSigned | ||
const signedTx = preSigned && tx.feePayer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if it's preSigned, why would it not have a feePayer? seems like there is an issue upstream? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no issue, just sort of a redundancy check but i can remove |
||
? tx | ||
: await this.prepareTx(tx, additionalSigners, opts); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why'd you get rid of the
?? [0]
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this way it will start undefined if not specified, then the call to
addUsers
will know no default is set and use the first userThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think this might break some backwards compatibility since not every user will call
addUsers