@drift-labs/sdk-browser
Version:
SDK for Drift Protocol
1,732 lines (1,545 loc) • 277 kB
text/typescript
import * as anchor from '@coral-xyz/anchor';
import {
AnchorProvider,
BN,
Idl,
Program,
ProgramAccount,
} from '@coral-xyz/anchor';
import { Idl as Idl30, Program as Program30 } from '@coral-xyz/anchor-30';
import bs58 from 'bs58';
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
createAssociatedTokenAccountInstruction,
createAssociatedTokenAccountIdempotentInstruction,
createCloseAccountInstruction,
createInitializeAccountInstruction,
getAssociatedTokenAddress,
TOKEN_2022_PROGRAM_ID,
TOKEN_PROGRAM_ID,
getAssociatedTokenAddressSync,
getMint,
getTransferHook,
getExtraAccountMetaAddress,
getExtraAccountMetas,
resolveExtraAccountMeta,
} from '@solana/spl-token';
import {
DriftClientMetricsEvents,
HighLeverageModeConfig,
isVariant,
IWallet,
MakerInfo,
MappedRecord,
MarketType,
ModifyOrderParams,
ModifyOrderPolicy,
OpenbookV2FulfillmentConfigAccount,
OptionalOrderParams,
OracleSource,
Order,
OrderParams,
OrderTriggerCondition,
OrderType,
PerpMarketAccount,
PerpMarketExtendedInfo,
PhoenixV1FulfillmentConfigAccount,
PlaceAndTakeOrderSuccessCondition,
PositionDirection,
ReferrerInfo,
ReferrerNameAccount,
SerumV3FulfillmentConfigAccount,
SettlePnlMode,
SignedTxData,
SpotBalanceType,
SpotMarketAccount,
SpotPosition,
StateAccount,
SwapReduceOnly,
SignedMsgOrderParamsMessage,
TakerInfo,
TxParams,
UserAccount,
UserStatsAccount,
ProtectedMakerModeConfig,
SignedMsgOrderParamsDelegateMessage,
TokenProgramFlag,
PostOnlyParams,
} from './types';
import driftIDL from './idl/drift.json';
import {
AccountMeta,
AddressLookupTableAccount,
BlockhashWithExpiryBlockHeight,
ConfirmOptions,
Connection,
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
Signer,
SystemProgram,
SYSVAR_CLOCK_PUBKEY,
SYSVAR_INSTRUCTIONS_PUBKEY,
Transaction,
TransactionInstruction,
TransactionSignature,
TransactionVersion,
VersionedTransaction,
} from '@solana/web3.js';
import { TokenFaucet } from './tokenFaucet';
import { EventEmitter } from 'events';
import StrictEventEmitter from 'strict-event-emitter-types';
import {
getDriftSignerPublicKey,
getDriftStateAccountPublicKey,
getFuelOverflowAccountPublicKey,
getHighLeverageModeConfigPublicKey,
getInsuranceFundStakeAccountPublicKey,
getOpenbookV2FulfillmentConfigPublicKey,
getPerpMarketPublicKey,
getPhoenixFulfillmentConfigPublicKey,
getProtectedMakerModeConfigPublicKey,
getPythLazerOraclePublicKey,
getPythPullOraclePublicKey,
getReferrerNamePublicKeySync,
getSerumFulfillmentConfigPublicKey,
getSerumSignerPublicKey,
getSpotMarketPublicKey,
getSignedMsgUserAccountPublicKey,
getUserAccountPublicKey,
getUserAccountPublicKeySync,
getUserStatsAccountPublicKey,
getSignedMsgWsDelegatesAccountPublicKey,
getIfRebalanceConfigPublicKey,
} from './addresses/pda';
import {
DataAndSlot,
DelistedMarketSetting,
DriftClientAccountEvents,
DriftClientAccountSubscriber,
} from './accounts/types';
import { TxSender, TxSigAndSlot } from './tx/types';
import {
BASE_PRECISION,
GOV_SPOT_MARKET_INDEX,
ONE,
PERCENTAGE_PRECISION,
PRICE_PRECISION,
QUOTE_PRECISION,
QUOTE_SPOT_MARKET_INDEX,
ZERO,
} from './constants/numericConstants';
import { findDirectionToClose, positionIsAvailable } from './math/position';
import { getSignedTokenAmount, getTokenAmount } from './math/spotBalance';
import { decodeName, DEFAULT_USER_NAME, encodeName } from './userName';
import { MMOraclePriceData, OraclePriceData } from './oracles/types';
import { DriftClientConfig } from './driftClientConfig';
import { PollingDriftClientAccountSubscriber } from './accounts/pollingDriftClientAccountSubscriber';
import { RetryTxSender } from './tx/retryTxSender';
import { User } from './user';
import { UserSubscriptionConfig } from './userConfig';
import {
configs,
DRIFT_ORACLE_RECEIVER_ID,
DEFAULT_CONFIRMATION_OPTS,
DRIFT_PROGRAM_ID,
DriftEnv,
PYTH_LAZER_STORAGE_ACCOUNT_KEY,
} from './config';
import { WRAPPED_SOL_MINT } from './constants/spotMarkets';
import { UserStats } from './userStats';
import { isSpotPositionAvailable } from './math/spotPosition';
import { calculateMarketMaxAvailableInsurance } from './math/market';
import { fetchUserStatsAccount } from './accounts/fetch';
import { castNumberToSpotPrecision } from './math/spotMarket';
import {
JupiterClient,
QuoteResponse,
SwapMode,
} from './jupiter/jupiterClient';
import { getNonIdleUserFilter } from './memcmp';
import { UserStatsSubscriptionConfig } from './userStatsConfig';
import { getMarinadeDepositIx, getMarinadeFinanceProgram } from './marinade';
import { getOrderParams, isUpdateHighLeverageMode } from './orderParams';
import { numberToSafeBN } from './math/utils';
import { TransactionParamProcessor } from './tx/txParamProcessor';
import { isOracleValid, trimVaaSignatures } from './math/oracles';
import { TxHandler } from './tx/txHandler';
import {
DEFAULT_RECEIVER_PROGRAM_ID,
wormholeCoreBridgeIdl,
} from '@pythnetwork/pyth-solana-receiver';
import { parseAccumulatorUpdateData } from '@pythnetwork/price-service-sdk';
import {
DEFAULT_WORMHOLE_PROGRAM_ID,
getGuardianSetPda,
} from '@pythnetwork/pyth-solana-receiver/lib/address';
import { WormholeCoreBridgeSolana } from '@pythnetwork/pyth-solana-receiver/lib/idl/wormhole_core_bridge_solana';
import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver/lib/idl/pyth_solana_receiver';
import { getFeedIdUint8Array, trimFeedId } from './util/pythOracleUtils';
import { createMinimalEd25519VerifyIx } from './util/ed25519Utils';
import {
createNativeInstructionDiscriminatorBuffer,
isVersionedTransaction,
} from './tx/utils';
import pythSolanaReceiverIdl from './idl/pyth_solana_receiver.json';
import { asV0Tx, PullFeed, AnchorUtils } from '@switchboard-xyz/on-demand';
import { gprcDriftClientAccountSubscriber } from './accounts/grpcDriftClientAccountSubscriber';
import nacl from 'tweetnacl';
import { Slothash } from './slot/SlothashSubscriber';
import { getOracleId } from './oracles/oracleId';
import { SignedMsgOrderParams } from './types';
import { sha256 } from '@noble/hashes/sha256';
import { getOracleConfidenceFromMMOracleData } from './oracles/utils';
import { Commitment } from 'gill';
import { WebSocketDriftClientAccountSubscriber } from './accounts/webSocketDriftClientAccountSubscriber';
type RemainingAccountParams = {
userAccounts: UserAccount[];
writablePerpMarketIndexes?: number[];
writableSpotMarketIndexes?: number[];
readablePerpMarketIndex?: number | number[];
readableSpotMarketIndexes?: number[];
useMarketLastSlotCache?: boolean;
};
/**
* # DriftClient
* This class is the main way to interact with Drift Protocol. It allows you to subscribe to the various accounts where the Market's state is stored, as well as: opening positions, liquidating, settling funding, depositing & withdrawing, and more.
*/
export class DriftClient {
connection: Connection;
wallet: IWallet;
public program: Program;
provider: AnchorProvider;
env: DriftEnv;
opts?: ConfirmOptions;
useHotWalletAdmin?: boolean;
users = new Map<string, User>();
userStats?: UserStats;
activeSubAccountId: number;
userAccountSubscriptionConfig: UserSubscriptionConfig;
userStatsAccountSubscriptionConfig: UserStatsSubscriptionConfig;
accountSubscriber: DriftClientAccountSubscriber;
eventEmitter: StrictEventEmitter<EventEmitter, DriftClientAccountEvents>;
metricsEventEmitter: StrictEventEmitter<
EventEmitter,
DriftClientMetricsEvents
>;
_isSubscribed = false;
txSender: TxSender;
perpMarketLastSlotCache = new Map<number, number>();
spotMarketLastSlotCache = new Map<number, number>();
mustIncludePerpMarketIndexes = new Set<number>();
mustIncludeSpotMarketIndexes = new Set<number>();
authority: PublicKey;
/** @deprecated use marketLookupTables */
marketLookupTable: PublicKey;
/** @deprecated use lookupTableAccounts */
lookupTableAccount: AddressLookupTableAccount;
marketLookupTables: PublicKey[];
lookupTableAccounts: AddressLookupTableAccount[];
includeDelegates?: boolean;
authoritySubAccountMap?: Map<string, number[]>;
skipLoadUsers?: boolean;
txVersion: TransactionVersion;
txParams: TxParams;
enableMetricsEvents?: boolean;
txHandler: TxHandler;
receiverProgram?: Program<PythSolanaReceiver>;
wormholeProgram?: Program<WormholeCoreBridgeSolana>;
sbOnDemandProgramdId: PublicKey;
sbOnDemandProgram?: Program30<Idl30>;
sbProgramFeedConfigs?: Map<string, any>;
public get isSubscribed() {
return this._isSubscribed && this.accountSubscriber.isSubscribed;
}
public set isSubscribed(val: boolean) {
this._isSubscribed = val;
}
public constructor(config: DriftClientConfig) {
this.connection = config.connection;
this.wallet = config.wallet;
this.env = config.env ?? 'mainnet-beta';
this.opts = config.opts || {
...DEFAULT_CONFIRMATION_OPTS,
};
this.useHotWalletAdmin = config.useHotWalletAdmin ?? false;
if (config?.connection?.commitment) {
// At the moment this ensures that our transaction simulations (which use Connection object) will use the same commitment level as our Transaction blockhashes (which use these opts)
this.opts.commitment = config.connection.commitment;
this.opts.preflightCommitment = config.connection.commitment;
}
this.provider = new AnchorProvider(
config.connection,
// @ts-ignore
config.wallet,
this.opts
);
this.program = new Program(
driftIDL as Idl,
config.programID ?? new PublicKey(DRIFT_PROGRAM_ID),
this.provider,
config.coder
);
this.authority = config.authority ?? this.wallet.publicKey;
this.activeSubAccountId = config.activeSubAccountId ?? 0;
this.skipLoadUsers = config.skipLoadUsers ?? false;
this.txVersion =
config.txVersion ?? this.getTxVersionForNewWallet(config.wallet);
this.txParams = {
computeUnits: config.txParams?.computeUnits ?? 600_000,
computeUnitsPrice: config.txParams?.computeUnitsPrice ?? 0,
};
this.txHandler =
config?.txHandler ??
new TxHandler({
connection: this.connection,
// @ts-ignore
wallet: this.provider.wallet,
confirmationOptions: this.opts,
opts: {
returnBlockHeightsWithSignedTxCallbackData:
config.enableMetricsEvents,
onSignedCb: this.handleSignedTransaction.bind(this),
preSignedCb: this.handlePreSignedTransaction.bind(this),
},
config: config.txHandlerConfig,
});
if (config.includeDelegates && config.subAccountIds) {
throw new Error(
'Can only pass one of includeDelegates or subAccountIds. If you want to specify subaccount ids for multiple authorities, pass authoritySubaccountMap instead'
);
}
if (config.authoritySubAccountMap && config.subAccountIds) {
throw new Error(
'Can only pass one of authoritySubaccountMap or subAccountIds'
);
}
if (config.authoritySubAccountMap && config.includeDelegates) {
throw new Error(
'Can only pass one of authoritySubaccountMap or includeDelegates'
);
}
this.authoritySubAccountMap = config.authoritySubAccountMap
? config.authoritySubAccountMap
: config.subAccountIds
? new Map([[this.authority.toString(), config.subAccountIds]])
: new Map<string, number[]>();
this.includeDelegates = config.includeDelegates ?? false;
if (config.accountSubscription?.type === 'polling') {
this.userAccountSubscriptionConfig = {
type: 'polling',
accountLoader: config.accountSubscription.accountLoader,
};
this.userStatsAccountSubscriptionConfig = {
type: 'polling',
accountLoader: config.accountSubscription.accountLoader,
};
} else if (config.accountSubscription?.type === 'grpc') {
this.userAccountSubscriptionConfig = {
type: 'grpc',
resubTimeoutMs: config.accountSubscription?.resubTimeoutMs,
logResubMessages: config.accountSubscription?.logResubMessages,
grpcConfigs: config.accountSubscription?.grpcConfigs,
};
this.userStatsAccountSubscriptionConfig = {
type: 'grpc',
grpcConfigs: config.accountSubscription?.grpcConfigs,
resubTimeoutMs: config.accountSubscription?.resubTimeoutMs,
logResubMessages: config.accountSubscription?.logResubMessages,
};
} else {
this.userAccountSubscriptionConfig = {
type: 'websocket',
resubTimeoutMs: config.accountSubscription?.resubTimeoutMs,
logResubMessages: config.accountSubscription?.logResubMessages,
commitment: config.accountSubscription?.commitment,
programUserAccountSubscriber:
config.accountSubscription?.programUserAccountSubscriber,
};
this.userStatsAccountSubscriptionConfig = {
type: 'websocket',
resubTimeoutMs: config.accountSubscription?.resubTimeoutMs,
logResubMessages: config.accountSubscription?.logResubMessages,
commitment: config.accountSubscription?.commitment,
};
}
if (config.userStats) {
this.userStats = new UserStats({
driftClient: this,
userStatsAccountPublicKey: getUserStatsAccountPublicKey(
this.program.programId,
this.authority
),
accountSubscription: this.userAccountSubscriptionConfig,
});
}
this.marketLookupTable = config.marketLookupTable;
if (!this.marketLookupTable) {
this.marketLookupTable = new PublicKey(
configs[this.env].MARKET_LOOKUP_TABLE
);
}
this.marketLookupTables = config.marketLookupTables;
if (!this.marketLookupTables) {
this.marketLookupTables = configs[this.env].MARKET_LOOKUP_TABLES.map(
(tableAddr) => new PublicKey(tableAddr)
);
}
const delistedMarketSetting =
config.delistedMarketSetting || DelistedMarketSetting.Unsubscribe;
const noMarketsAndOraclesSpecified =
config.perpMarketIndexes === undefined &&
config.spotMarketIndexes === undefined &&
config.oracleInfos === undefined;
if (config.accountSubscription?.type === 'polling') {
this.accountSubscriber = new PollingDriftClientAccountSubscriber(
this.program,
config.accountSubscription.accountLoader,
config.perpMarketIndexes ?? [],
config.spotMarketIndexes ?? [],
config.oracleInfos ?? [],
noMarketsAndOraclesSpecified,
delistedMarketSetting
);
} else if (config.accountSubscription?.type === 'grpc') {
this.accountSubscriber = new gprcDriftClientAccountSubscriber(
config.accountSubscription.grpcConfigs,
this.program,
config.perpMarketIndexes ?? [],
config.spotMarketIndexes ?? [],
config.oracleInfos ?? [],
noMarketsAndOraclesSpecified,
delistedMarketSetting,
{
resubTimeoutMs: config.accountSubscription?.resubTimeoutMs,
logResubMessages: config.accountSubscription?.logResubMessages,
}
);
} else {
const accountSubscriberClass =
config.accountSubscription?.driftClientAccountSubscriber ??
WebSocketDriftClientAccountSubscriber;
this.accountSubscriber = new accountSubscriberClass(
this.program,
config.perpMarketIndexes ?? [],
config.spotMarketIndexes ?? [],
config.oracleInfos ?? [],
noMarketsAndOraclesSpecified,
delistedMarketSetting,
{
resubTimeoutMs: config.accountSubscription?.resubTimeoutMs,
logResubMessages: config.accountSubscription?.logResubMessages,
},
config.accountSubscription?.commitment as Commitment
);
}
this.eventEmitter = this.accountSubscriber.eventEmitter;
this.metricsEventEmitter = new EventEmitter();
if (config.enableMetricsEvents) {
this.enableMetricsEvents = true;
}
this.txSender =
config.txSender ??
new RetryTxSender({
connection: this.connection,
wallet: this.wallet,
opts: this.opts,
txHandler: this.txHandler,
});
this.sbOnDemandProgramdId = configs[this.env].SB_ON_DEMAND_PID;
}
public getUserMapKey(subAccountId: number, authority: PublicKey): string {
return `${subAccountId}_${authority.toString()}`;
}
createUser(
subAccountId: number,
accountSubscriptionConfig: UserSubscriptionConfig,
authority?: PublicKey
): User {
const userAccountPublicKey = getUserAccountPublicKeySync(
this.program.programId,
authority ?? this.authority,
subAccountId
);
return new User({
driftClient: this,
userAccountPublicKey,
accountSubscription: accountSubscriptionConfig,
});
}
public async subscribe(): Promise<boolean> {
let subscribePromises = [this.addAndSubscribeToUsers()].concat(
this.accountSubscriber.subscribe()
);
if (this.userStats !== undefined) {
subscribePromises = subscribePromises.concat(this.userStats.subscribe());
}
this.isSubscribed = (await Promise.all(subscribePromises)).reduce(
(success, prevSuccess) => success && prevSuccess
);
return this.isSubscribed;
}
subscribeUsers(): Promise<boolean>[] {
return [...this.users.values()].map((user) => user.subscribe());
}
/**
* Forces the accountSubscriber to fetch account updates from rpc
*/
public async fetchAccounts(): Promise<void> {
let promises = [...this.users.values()]
.map((user) => user.fetchAccounts())
.concat(this.accountSubscriber.fetch());
if (this.userStats) {
promises = promises.concat(this.userStats.fetchAccounts());
}
await Promise.all(promises);
}
public async unsubscribe(): Promise<void> {
let unsubscribePromises = this.unsubscribeUsers().concat(
this.accountSubscriber.unsubscribe()
);
if (this.userStats !== undefined) {
unsubscribePromises = unsubscribePromises.concat(
this.userStats.unsubscribe()
);
}
await Promise.all(unsubscribePromises);
this.isSubscribed = false;
}
unsubscribeUsers(): Promise<void>[] {
return [...this.users.values()].map((user) => user.unsubscribe());
}
statePublicKey?: PublicKey;
public async getStatePublicKey(): Promise<PublicKey> {
if (this.statePublicKey) {
return this.statePublicKey;
}
this.statePublicKey = await getDriftStateAccountPublicKey(
this.program.programId
);
return this.statePublicKey;
}
signerPublicKey?: PublicKey;
public getSignerPublicKey(): PublicKey {
if (this.signerPublicKey) {
return this.signerPublicKey;
}
this.signerPublicKey = getDriftSignerPublicKey(this.program.programId);
return this.signerPublicKey;
}
public getStateAccount(): StateAccount {
return this.accountSubscriber.getStateAccountAndSlot().data;
}
/**
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
*/
public async forceGetStateAccount(): Promise<StateAccount> {
await this.accountSubscriber.fetch();
return this.accountSubscriber.getStateAccountAndSlot().data;
}
public getPerpMarketAccount(
marketIndex: number
): PerpMarketAccount | undefined {
return this.accountSubscriber.getMarketAccountAndSlot(marketIndex)?.data;
}
/**
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
* @param marketIndex
*/
public async forceGetPerpMarketAccount(
marketIndex: number
): Promise<PerpMarketAccount | undefined> {
await this.accountSubscriber.fetch();
let data =
this.accountSubscriber.getMarketAccountAndSlot(marketIndex)?.data;
let i = 0;
while (data === undefined && i < 10) {
await this.accountSubscriber.fetch();
data = this.accountSubscriber.getMarketAccountAndSlot(marketIndex)?.data;
i++;
}
return data;
}
public getPerpMarketAccounts(): PerpMarketAccount[] {
return this.accountSubscriber
.getMarketAccountsAndSlots()
.filter((value) => value !== undefined)
.map((value) => value.data);
}
public getSpotMarketAccount(
marketIndex: number
): SpotMarketAccount | undefined {
return this.accountSubscriber.getSpotMarketAccountAndSlot(marketIndex)
?.data;
}
/**
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
* @param marketIndex
*/
public async forceGetSpotMarketAccount(
marketIndex: number
): Promise<SpotMarketAccount | undefined> {
await this.accountSubscriber.fetch();
return this.accountSubscriber.getSpotMarketAccountAndSlot(marketIndex)
?.data;
}
public getSpotMarketAccounts(): SpotMarketAccount[] {
return this.accountSubscriber
.getSpotMarketAccountsAndSlots()
.filter((value) => value !== undefined)
.map((value) => value.data);
}
public getQuoteSpotMarketAccount(): SpotMarketAccount {
return this.accountSubscriber.getSpotMarketAccountAndSlot(
QUOTE_SPOT_MARKET_INDEX
).data;
}
public getOraclePriceDataAndSlot(
oraclePublicKey: PublicKey,
oracleSource: OracleSource
): DataAndSlot<OraclePriceData> | undefined {
return this.accountSubscriber.getOraclePriceDataAndSlot(
getOracleId(oraclePublicKey, oracleSource)
);
}
public async getSerumV3FulfillmentConfig(
serumMarket: PublicKey
): Promise<SerumV3FulfillmentConfigAccount> {
const address = await getSerumFulfillmentConfigPublicKey(
this.program.programId,
serumMarket
);
return (await this.program.account.serumV3FulfillmentConfig.fetch(
address
)) as SerumV3FulfillmentConfigAccount;
}
public async getSerumV3FulfillmentConfigs(): Promise<
SerumV3FulfillmentConfigAccount[]
> {
const accounts = await this.program.account.serumV3FulfillmentConfig.all();
return accounts.map(
(account) => account.account
) as SerumV3FulfillmentConfigAccount[];
}
public async getPhoenixV1FulfillmentConfig(
phoenixMarket: PublicKey
): Promise<PhoenixV1FulfillmentConfigAccount> {
const address = await getPhoenixFulfillmentConfigPublicKey(
this.program.programId,
phoenixMarket
);
return (await this.program.account.phoenixV1FulfillmentConfig.fetch(
address
)) as PhoenixV1FulfillmentConfigAccount;
}
public async getPhoenixV1FulfillmentConfigs(): Promise<
PhoenixV1FulfillmentConfigAccount[]
> {
const accounts =
await this.program.account.phoenixV1FulfillmentConfig.all();
return accounts.map(
(account) => account.account
) as PhoenixV1FulfillmentConfigAccount[];
}
public async getOpenbookV2FulfillmentConfig(
openbookMarket: PublicKey
): Promise<OpenbookV2FulfillmentConfigAccount> {
const address = getOpenbookV2FulfillmentConfigPublicKey(
this.program.programId,
openbookMarket
);
return (await this.program.account.openbookV2FulfillmentConfig.fetch(
address
)) as OpenbookV2FulfillmentConfigAccount;
}
public async getOpenbookV2FulfillmentConfigs(): Promise<
OpenbookV2FulfillmentConfigAccount[]
> {
const accounts =
await this.program.account.openbookV2FulfillmentConfig.all();
return accounts.map(
(account) => account.account
) as OpenbookV2FulfillmentConfigAccount[];
}
/** @deprecated use fetchAllLookupTableAccounts() */
public async fetchMarketLookupTableAccount(): Promise<AddressLookupTableAccount> {
if (this.lookupTableAccount) return this.lookupTableAccount;
if (!this.marketLookupTable) {
console.log('Market lookup table address not set');
return;
}
const lookupTableAccount = (
await this.connection.getAddressLookupTable(this.marketLookupTable)
).value;
this.lookupTableAccount = lookupTableAccount;
return lookupTableAccount;
}
public async fetchAllLookupTableAccounts(): Promise<
AddressLookupTableAccount[]
> {
if (this.lookupTableAccounts) return this.lookupTableAccounts;
if (!this.marketLookupTables) {
console.log('Market lookup table address not set');
return;
}
const lookupTableAccountResults = await Promise.all(
this.marketLookupTables.map((lookupTable) =>
this.connection.getAddressLookupTable(lookupTable)
)
);
const lookupTableAccounts = lookupTableAccountResults.map(
(result) => result.value
);
this.lookupTableAccounts = lookupTableAccounts;
return lookupTableAccounts;
}
private getTxVersionForNewWallet(newWallet: IWallet) {
if (!newWallet?.supportedTransactionVersions) return 0; // Assume versioned txs supported if wallet doesn't have a supportedTransactionVersions property
const walletSupportsVersionedTxns =
newWallet.supportedTransactionVersions?.has(0) ||
(newWallet.supportedTransactionVersions?.size ?? 0) > 1;
return walletSupportsVersionedTxns ? 0 : 'legacy';
}
/**
* Update the wallet to use for drift transactions and linked user account
* @param newWallet
* @param subAccountIds
* @param activeSubAccountId
* @param includeDelegates
*/
public async updateWallet(
newWallet: IWallet,
subAccountIds?: number[],
activeSubAccountId?: number,
includeDelegates?: boolean,
authoritySubaccountMap?: Map<string, number[]>
): Promise<boolean> {
const newProvider = new AnchorProvider(
this.connection,
// @ts-ignore
newWallet,
this.opts
);
const newProgram = new Program(
driftIDL as Idl,
this.program.programId,
newProvider
);
this.skipLoadUsers = false;
// Update provider for txSender with new wallet details
this.txSender.wallet = newWallet;
this.wallet = newWallet;
this.txHandler.updateWallet(newWallet);
this.provider = newProvider;
this.program = newProgram;
this.authority = newWallet.publicKey;
this.activeSubAccountId = activeSubAccountId;
this.userStatsAccountPublicKey = undefined;
this.includeDelegates = includeDelegates ?? false;
this.txVersion = this.getTxVersionForNewWallet(this.wallet);
if (includeDelegates && subAccountIds) {
throw new Error(
'Can only pass one of includeDelegates or subAccountIds. If you want to specify subaccount ids for multiple authorities, pass authoritySubaccountMap instead'
);
}
if (authoritySubaccountMap && subAccountIds) {
throw new Error(
'Can only pass one of authoritySubaccountMap or subAccountIds'
);
}
if (authoritySubaccountMap && includeDelegates) {
throw new Error(
'Can only pass one of authoritySubaccountMap or includeDelegates'
);
}
this.authoritySubAccountMap = authoritySubaccountMap
? authoritySubaccountMap
: subAccountIds
? new Map([[this.authority.toString(), subAccountIds]])
: new Map<string, number[]>();
/* Reset user stats account */
if (this.userStats?.isSubscribed) {
await this.userStats.unsubscribe();
}
this.userStats = undefined;
this.userStats = new UserStats({
driftClient: this,
userStatsAccountPublicKey: this.getUserStatsAccountPublicKey(),
accountSubscription: this.userStatsAccountSubscriptionConfig,
});
const subscriptionPromises: Promise<any>[] = [this.userStats.subscribe()];
let success = true;
if (this.isSubscribed) {
const reSubscribeUsersPromise = async () => {
await Promise.all(this.unsubscribeUsers());
this.users.clear();
success = await this.addAndSubscribeToUsers();
};
subscriptionPromises.push(reSubscribeUsersPromise());
}
await Promise.all(subscriptionPromises);
return success;
}
/**
* Update the subscribed accounts to a given authority, while leaving the
* connected wallet intact. This allows a user to emulate another user's
* account on the UI and sign permissionless transactions with their own wallet.
* @param emulateAuthority
*/
public async emulateAccount(emulateAuthority: PublicKey): Promise<boolean> {
this.skipLoadUsers = false;
// Update provider for txSender with new wallet details
this.authority = emulateAuthority;
this.userStatsAccountPublicKey = undefined;
this.includeDelegates = true;
this.txVersion = this.getTxVersionForNewWallet(this.wallet);
this.authoritySubAccountMap = new Map<string, number[]>();
/* Reset user stats account */
if (this.userStats?.isSubscribed) {
await this.userStats.unsubscribe();
}
this.userStats = undefined;
this.userStats = new UserStats({
driftClient: this,
userStatsAccountPublicKey: this.getUserStatsAccountPublicKey(),
accountSubscription: this.userStatsAccountSubscriptionConfig,
});
await this.userStats.subscribe();
let success = true;
if (this.isSubscribed) {
await Promise.all(this.unsubscribeUsers());
this.users.clear();
success = await this.addAndSubscribeToUsers(emulateAuthority);
}
return success;
}
public async switchActiveUser(subAccountId: number, authority?: PublicKey) {
const authorityChanged = authority && !this.authority?.equals(authority);
this.activeSubAccountId = subAccountId;
this.authority = authority ?? this.authority;
this.userStatsAccountPublicKey = getUserStatsAccountPublicKey(
this.program.programId,
this.authority
);
/* If changing the user authority ie switching from delegate to non-delegate account, need to re-subscribe to the user stats account */
if (authorityChanged && this.userStats) {
if (this.userStats.isSubscribed) {
await this.userStats.unsubscribe();
}
this.userStats = new UserStats({
driftClient: this,
userStatsAccountPublicKey: this.userStatsAccountPublicKey,
accountSubscription: this.userAccountSubscriptionConfig,
});
this.userStats.subscribe();
}
}
public async addUser(
subAccountId: number,
authority?: PublicKey,
userAccount?: UserAccount
): Promise<boolean> {
authority = authority ?? this.authority;
const userKey = this.getUserMapKey(subAccountId, authority);
if (this.users.has(userKey) && this.users.get(userKey).isSubscribed) {
return true;
}
const user = this.createUser(
subAccountId,
this.userAccountSubscriptionConfig,
authority
);
const result = await user.subscribe(userAccount);
if (result) {
this.users.set(userKey, user);
return true;
} else {
return false;
}
}
/**
* Adds and subscribes to users based on params set by the constructor or by updateWallet.
*/
public async addAndSubscribeToUsers(authority?: PublicKey): Promise<boolean> {
// save the rpc calls if driftclient is initialized without a real wallet
if (this.skipLoadUsers) return true;
let result = true;
if (this.authoritySubAccountMap && this.authoritySubAccountMap.size > 0) {
this.authoritySubAccountMap.forEach(async (value, key) => {
for (const subAccountId of value) {
result =
result && (await this.addUser(subAccountId, new PublicKey(key)));
}
});
if (this.activeSubAccountId == undefined) {
this.switchActiveUser(
[...this.authoritySubAccountMap.values()][0][0] ?? 0,
new PublicKey(
[...this.authoritySubAccountMap.keys()][0] ??
this.authority.toString()
)
);
}
} else {
let userAccounts = [];
let delegatedAccounts = [];
const userAccountsPromise = this.getUserAccountsForAuthority(
authority ?? this.wallet.publicKey
);
if (this.includeDelegates) {
const delegatedAccountsPromise = this.getUserAccountsForDelegate(
authority ?? this.wallet.publicKey
);
[userAccounts, delegatedAccounts] = await Promise.all([
userAccountsPromise,
delegatedAccountsPromise,
]);
!userAccounts && (userAccounts = []);
!delegatedAccounts && (delegatedAccounts = []);
} else {
userAccounts = (await userAccountsPromise) ?? [];
}
const allAccounts = userAccounts.concat(delegatedAccounts);
const addAllAccountsPromise = allAccounts.map((acc) =>
this.addUser(acc.subAccountId, acc.authority, acc)
);
const addAllAccountsResults = await Promise.all(addAllAccountsPromise);
result = addAllAccountsResults.every((res) => !!res);
if (this.activeSubAccountId == undefined) {
this.switchActiveUser(
userAccounts.concat(delegatedAccounts)[0]?.subAccountId ?? 0,
userAccounts.concat(delegatedAccounts)[0]?.authority ?? this.authority
);
}
}
return result;
}
/**
* Returns the instructions to initialize a user account and the public key of the user account.
* @param subAccountId
* @param name
* @param referrerInfo
* @returns [instructions, userAccountPublicKey]
*/
public async getInitializeUserAccountIxs(
subAccountId = 0,
name?: string,
referrerInfo?: ReferrerInfo,
poolId?: number
): Promise<[TransactionInstruction[], PublicKey]> {
const initializeIxs: TransactionInstruction[] = [];
const [userAccountPublicKey, initializeUserAccountIx] =
await this.getInitializeUserInstructions(
subAccountId,
name,
referrerInfo
);
if (subAccountId === 0) {
if (
!(await this.checkIfAccountExists(this.getUserStatsAccountPublicKey()))
) {
initializeIxs.push(await this.getInitializeUserStatsIx());
}
}
initializeIxs.push(initializeUserAccountIx);
if (poolId) {
initializeIxs.push(
await this.getUpdateUserPoolIdIx(poolId, subAccountId)
);
}
return [initializeIxs, userAccountPublicKey];
}
/**
* Initializes a user account and returns the transaction signature and the public key of the user account.
* @param subAccountId
* @param name
* @param referrerInfo
* @param txParams
* @returns [transactionSignature, userAccountPublicKey]
*/
public async initializeUserAccount(
subAccountId = 0,
name?: string,
referrerInfo?: ReferrerInfo,
txParams?: TxParams
): Promise<[TransactionSignature, PublicKey]> {
const [initializeIxs, userAccountPublicKey] =
await this.getInitializeUserAccountIxs(subAccountId, name, referrerInfo);
const tx = await this.buildTransaction(initializeIxs, txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
await this.addUser(subAccountId);
return [txSig, userAccountPublicKey];
}
async getInitializeUserStatsIx(): Promise<TransactionInstruction> {
return await this.program.instruction.initializeUserStats({
accounts: {
userStats: getUserStatsAccountPublicKey(
this.program.programId,
this.wallet.publicKey // only allow payer to initialize own user stats account
),
authority: this.wallet.publicKey,
payer: this.wallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
state: await this.getStatePublicKey(),
},
});
}
public async initializeSignedMsgUserOrders(
authority: PublicKey,
numOrders: number,
txParams?: TxParams
): Promise<[TransactionSignature, PublicKey]> {
const initializeIxs = [];
const [signedMsgUserAccountPublicKey, initializeUserAccountIx] =
await this.getInitializeSignedMsgUserOrdersAccountIx(
authority,
numOrders
);
initializeIxs.push(initializeUserAccountIx);
const tx = await this.buildTransaction(initializeIxs, txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return [txSig, signedMsgUserAccountPublicKey];
}
async getInitializeSignedMsgUserOrdersAccountIx(
authority: PublicKey,
numOrders: number
): Promise<[PublicKey, TransactionInstruction]> {
const signedMsgUserAccountPublicKey = getSignedMsgUserAccountPublicKey(
this.program.programId,
authority
);
const initializeUserAccountIx =
await this.program.instruction.initializeSignedMsgUserOrders(numOrders, {
accounts: {
signedMsgUserOrders: signedMsgUserAccountPublicKey,
authority,
payer: this.wallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
});
return [signedMsgUserAccountPublicKey, initializeUserAccountIx];
}
public async resizeSignedMsgUserOrders(
authority: PublicKey,
numOrders: number,
userSubaccountId?: number,
txParams?: TxParams
): Promise<TransactionSignature> {
const resizeUserAccountIx =
await this.getResizeSignedMsgUserOrdersInstruction(
authority,
numOrders,
userSubaccountId
);
const tx = await this.buildTransaction([resizeUserAccountIx], txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
async getResizeSignedMsgUserOrdersInstruction(
authority: PublicKey,
numOrders: number,
userSubaccountId?: number
): Promise<TransactionInstruction> {
const signedMsgUserAccountPublicKey = getSignedMsgUserAccountPublicKey(
this.program.programId,
authority
);
const resizeUserAccountIx =
await this.program.instruction.resizeSignedMsgUserOrders(numOrders, {
accounts: {
signedMsgUserOrders: signedMsgUserAccountPublicKey,
authority,
payer: this.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
user: await getUserAccountPublicKey(
this.program.programId,
authority,
userSubaccountId
),
},
});
return resizeUserAccountIx;
}
public async initializeSignedMsgWsDelegatesAccount(
authority: PublicKey,
delegates: PublicKey[] = [],
txParams?: TxParams
): Promise<TransactionSignature> {
const ix = await this.getInitializeSignedMsgWsDelegatesAccountIx(
authority,
delegates
);
const tx = await this.buildTransaction([ix], txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async getInitializeSignedMsgWsDelegatesAccountIx(
authority: PublicKey,
delegates: PublicKey[] = []
): Promise<TransactionInstruction> {
const signedMsgWsDelegates = getSignedMsgWsDelegatesAccountPublicKey(
this.program.programId,
authority
);
const ix = await this.program.instruction.initializeSignedMsgWsDelegates(
delegates,
{
accounts: {
signedMsgWsDelegates,
authority: this.wallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
}
);
return ix;
}
public async addSignedMsgWsDelegate(
authority: PublicKey,
delegate: PublicKey,
txParams?: TxParams
): Promise<TransactionSignature> {
const ix = await this.getAddSignedMsgWsDelegateIx(authority, delegate);
const tx = await this.buildTransaction([ix], txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async getAddSignedMsgWsDelegateIx(
authority: PublicKey,
delegate: PublicKey
): Promise<TransactionInstruction> {
const signedMsgWsDelegates = getSignedMsgWsDelegatesAccountPublicKey(
this.program.programId,
authority
);
const ix = await this.program.instruction.changeSignedMsgWsDelegateStatus(
delegate,
true,
{
accounts: {
signedMsgWsDelegates,
authority: this.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
},
}
);
return ix;
}
public async removeSignedMsgWsDelegate(
authority: PublicKey,
delegate: PublicKey,
txParams?: TxParams
): Promise<TransactionSignature> {
const ix = await this.getRemoveSignedMsgWsDelegateIx(authority, delegate);
const tx = await this.buildTransaction([ix], txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async getRemoveSignedMsgWsDelegateIx(
authority: PublicKey,
delegate: PublicKey
): Promise<TransactionInstruction> {
const signedMsgWsDelegates = getSignedMsgWsDelegatesAccountPublicKey(
this.program.programId,
authority
);
const ix = await this.program.instruction.changeSignedMsgWsDelegateStatus(
delegate,
false,
{
accounts: {
signedMsgWsDelegates,
authority: this.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
},
}
);
return ix;
}
public async initializeFuelOverflow(
authority?: PublicKey
): Promise<TransactionSignature> {
const ix = await this.getInitializeFuelOverflowIx(authority);
const tx = await this.buildTransaction([ix], this.txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async getInitializeFuelOverflowIx(
authority?: PublicKey
): Promise<TransactionInstruction> {
return await this.program.instruction.initializeFuelOverflow({
accounts: {
fuelOverflow: getFuelOverflowAccountPublicKey(
this.program.programId,
authority ?? this.wallet.publicKey
),
userStats: getUserStatsAccountPublicKey(
this.program.programId,
authority ?? this.wallet.publicKey
),
authority: authority ?? this.wallet.publicKey,
payer: this.wallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
});
}
public async sweepFuel(authority?: PublicKey): Promise<TransactionSignature> {
const ix = await this.getSweepFuelIx(authority);
const tx = await this.buildTransaction([ix], this.txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async getSweepFuelIx(
authority?: PublicKey
): Promise<TransactionInstruction> {
return await this.program.instruction.sweepFuel({
accounts: {
fuelOverflow: getFuelOverflowAccountPublicKey(
this.program.programId,
authority ?? this.wallet.publicKey
),
userStats: getUserStatsAccountPublicKey(
this.program.programId,
authority ?? this.wallet.publicKey
),
authority: authority ?? this.wallet.publicKey,
signer: this.wallet.publicKey,
},
});
}
private async getInitializeUserInstructions(
subAccountId = 0,
name?: string,
referrerInfo?: ReferrerInfo
): Promise<[PublicKey, TransactionInstruction]> {
const userAccountPublicKey = await getUserAccountPublicKey(
this.program.programId,
this.wallet.publicKey,
subAccountId
);
const remainingAccounts = new Array<AccountMeta>();
if (referrerInfo !== undefined) {
remainingAccounts.push({
pubkey: referrerInfo.referrer,
isWritable: true,
isSigner: false,
});
remainingAccounts.push({
pubkey: referrerInfo.referrerStats,
isWritable: true,
isSigner: false,
});
}
const state = this.getStateAccount();
if (!state.whitelistMint.equals(PublicKey.default)) {
const associatedTokenPublicKey = await getAssociatedTokenAddress(
state.whitelistMint,
this.wallet.publicKey
);
remainingAccounts.push({
pubkey: associatedTokenPublicKey,
isWritable: false,
isSigner: false,
});
}
if (name === undefined) {
if (subAccountId === 0) {
name = DEFAULT_USER_NAME;
} else {
name = `Subaccount ${subAccountId + 1}`;
}
}
const nameBuffer = encodeName(name);
const initializeUserAccountIx =
await this.program.instruction.initializeUser(subAccountId, nameBuffer, {
accounts: {
user: userAccountPublicKey,
userStats: this.getUserStatsAccountPublicKey(),
authority: this.wallet.publicKey,
payer: this.wallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
state: await this.getStatePublicKey(),
},
remainingAccounts,
});
return [userAccountPublicKey, initializeUserAccountIx];
}
async getNextSubAccountId(): Promise<number> {
const userStats = this.getUserStats();
let userStatsAccount: UserStatsAccount;
if (!userStats) {
userStatsAccount = await fetchUserStatsAccount(
this.connection,
this.program,
this.wallet.publicKey
);
} else {
userStatsAccount = userStats.getAccount();
}
return userStatsAccount.numberOfSubAccountsCreated;
}
public async initializeReferrerName(
name: string
): Promise<TransactionSignature> {
const userAccountPublicKey = getUserAccountPublicKeySync(
this.program.programId,
this.wallet.publicKey,
0
);
const nameBuffer = encodeName(name);
const referrerNameAccountPublicKey = getReferrerNamePublicKeySync(
this.program.programId,
nameBuffer
);
const tx = await this.program.transaction.initializeReferrerName(
nameBuffer,
{
accounts: {
referrerName: referrerNameAccountPublicKey,
user: userAccountPublicKey,
authority: this.wallet.publicKey,
userStats: this.getUserStatsAccountPublicKey(),
payer: this.wallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
}
);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async updateUserName(
name: string,
subAccountId = 0
): Promise<TransactionSignature> {
const userAccountPublicKey = getUserAccountPublicKeySync(
this.program.programId,
this.wallet.publicKey,
subAccountId
);
const nameBuffer = encodeName(name);
const tx = await this.program.transaction.updateUserName(
subAccountId,
nameBuffer,
{
accounts: {
user: userAccountPublicKey,
authority: this.wallet.publicKey,
},
}
);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async updateUserCustomMarginRatio(
updates: { marginRatio: number; subAccountId: number }[],
txParams?: TxParams
): Promise<TransactionSignature> {
const ixs = await Promise.all(
updates.map(async ({ marginRatio, subAccountId }) => {
const ix = await this.getUpdateUserCustomMarginRatioIx(
marginRatio,
subAccountId
);
return ix;
})
);
const tx = await this.buildTransaction(ixs, txParams ?? this.txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async getUpdateUserCustomMarginRatioIx(
marginRatio: number,
subAccountId = 0
): Promise<TransactionInstruction> {
const userAccountPublicKey = getUserAccountPublicKeySync(
this.program.programId,
this.wallet.publicKey,
subAccountId
);
await this.addUser(subAccountId, this.wallet.publicKey);
const ix = this.program.instruction.updateUserCustomMarginRatio(
subAccountId,
marginRatio,
{
accounts: {
user: userAccountPublicKey,
authority: this.wallet.publicKey,
},
}
);
return ix;
}
public async getUpdateUserPerpPositionCustomMarginRatioIx(
perpMarketIndex: number,
marginRatio: number,
subAccountId = 0
): Promise<TransactionInstruction> {
const userAccountPublicKey = getUserAccountPublicKeySync(
this.program.programId,
this.wallet.publicKey,
subAccountId
);
await this.addUser(subAccountId, this.wallet.publicKey);
const ix = this.program.instruction.updateUserPerpPositionCustomMarginRatio(
subAccountId,
perpMarketIndex,
marginRatio,
{
accounts: {
user: userAccountPublicKey,
authority: this.wallet.publicKey,
},
}
);
return ix;
}
public async updateUserPerpPositionCustomMarginRatio(
perpMarketIndex: number,
marginRatio: number,
subAccountId = 0,
txParams?: TxParams
): Promise<TransactionSignature> {
const ix = await this.getUpdateUserPerpPositionCustomMarginRatioIx(
perpMarketIndex,
marginRatio,
subAccountId
);
const tx = await this.buildTransaction(ix, txParams ?? this.txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async getUpdateUserMarginTradingEnabledIx(
marginTradingEnabled: boolean,
subAccountId = 0,
userAccountPublicKey?: PublicKey
): Promise<TransactionInstruction> {
const userAccountPublicKeyToUse =
userAccountPublicKey ||
getUserAccountPublicKeySync(
this.program.programId,
this.wallet.publicKey,
subAccountId
);
await this.addUser(subAccountId, this.wallet.publicKey);
let remainingAccounts;
try {
remainingAccounts = this.getRemainingAccounts({
userAccounts: [this.getUserAccount(subAccountId)],
});
} catch (err) {
remainingAccounts = [];
}
return await this.program.instruction.updateUserMarginTradingEnabled(
subAccountId,
marginTradingEnabled,
{
accounts: {
user: userAccountPublicKeyToUse,
authority: this.wallet.publicKey,
},
remainingAccounts,
}
);
}
public async updateUserMarginTradingEnabled(
updates: { marginTradingEnabled: boolean; subAccountId: number }[]
): Promise<TransactionSignature> {
const ixs = await Promise.all(
updates.map(async ({ marginTradingEnabled, subAccountId }) => {
return await this.getUpdateUserMarginTradingEnabledIx(
marginTradingEnabled,
subAccountId
);
})
);
const tx = await this.buildTransaction(ixs, this.txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async getUpdateUserDelegateIx(
delegate: PublicKey,
overrides: {
subAccountId?: number;
userAccountPublicKey?: PublicKey;
authority?: PublicKey;
}
): Promise<TransactionInstruction> {
const subAccountId = overrides.subAccountId ?? this.activeSubAccountId;
const userAccountPublicKey =
overrides.userAccountPublicKey ?? (await this.getUserAccountPublicKey());
const authority = overrides.authority ?? this.wallet.publicKey;
return await this.program.instruction.updateUserDelegate(
subAccountId,
delegate,
{
accounts: {
user: userAccountPublicKey,
authority,
},
}
);
}
public async updateUserDelegate(
delegate: PublicKey,
subAccountId = 0
): Promise<TransactionSignature> {
const tx = await this.program.transaction.updateUserDelegate(
subAccountId,
delegate,
{
accounts: {
user: await this.getUserAccountPublicKey(),
authority: this.wallet.publicKey,
},
}
);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async updateUserAdvancedLp(
updates: { advancedLp: boolean; subAccountId: number }[]
): Promise<TransactionSignature> {
const ixs = await Promise.all(
updates.map(async ({ advancedLp, subAccountId }) => {
return await this.getUpdateAdvancedDlpIx(advancedLp, subAccountId);
})
);
const tx = await this.buildTransaction(ixs, this.txParams);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}
public async getUpdateAdvancedDlpIx(
advancedLp: boolean,
subAccountId: number
) {
const ix = await this.program.instruction.updateUserAdvancedLp(
subAccountId,
advancedLp,
{
accounts: {
user: getUserAccountPublicKeySync(
this.program.programId,
this.wallet.publicKey,
subAccountId
),
authority: this.wallet.publicKey,
},
}
);
return ix;
}
public async updateUserReduceOnly(
updates: { reduceOnly: boolean; subAccountId: number }[]
): Promise<TransactionSignature> {
const ixs = await Promise.all(
updates.map(async ({ reduceOnly, subAccountId }) => {
return await this.getUpdateUserReduceOnlyIx(reduceOnly, subAccountId);
})
);
const tx = await this.buildTransaction(ixs, this.txParams);
const { txSig } = await this.s