diff --git a/common-ts/src/drift/Drift/clients/CentralServerDrift/index.ts b/common-ts/src/drift/Drift/clients/CentralServerDrift/index.ts index 96e85384..c55cf7f9 100644 --- a/common-ts/src/drift/Drift/clients/CentralServerDrift/index.ts +++ b/common-ts/src/drift/Drift/clients/CentralServerDrift/index.ts @@ -356,6 +356,11 @@ export class CentralServerDrift { fromSubAccountId?: number; customMaxMarginRatio?: number; txParams?: TxParams; + /** + * Optional external wallet to deposit from. If provided, the deposit will be made + * from this wallet instead of the authority wallet. + */ + externalWallet?: PublicKey; } ): Promise<{ transaction: VersionedTransaction | Transaction; @@ -381,8 +386,10 @@ export class CentralServerDrift { const originalWallet = this._driftClient.wallet; const originalAuthority = this._driftClient.authority; + // Use external wallet for transaction signing context if provided + const walletPublicKey = options?.externalWallet ?? authority; const authorityWallet = { - publicKey: authority, + publicKey: walletPublicKey, signTransaction: () => Promise.reject('This is a placeholder - do not sign with this wallet'), signAllTransactions: () => @@ -395,6 +402,9 @@ export class CentralServerDrift { this._driftClient.provider.wallet = authorityWallet; this._driftClient.txHandler.updateWallet(authorityWallet); this._driftClient.authority = authority; + // Clear userStatsAccountPublicKey cache so it's recalculated for the new authority + // @ts-ignore - accessing private property for cache invalidation + this._driftClient.userStatsAccountPublicKey = undefined; return await createUserAndDepositCollateralBaseTxn({ driftClient: this._driftClient, @@ -408,6 +418,7 @@ export class CentralServerDrift { fromSubAccountId: options?.fromSubAccountId, customMaxMarginRatio: options?.customMaxMarginRatio, txParams: options?.txParams ?? this.getTxParams(), + externalWallet: options?.externalWallet, }); } finally { this._driftClient.wallet = originalWallet; diff --git a/common-ts/src/drift/base/actions/user/create.ts b/common-ts/src/drift/base/actions/user/create.ts index 39a83ce9..3aba74eb 100644 --- a/common-ts/src/drift/base/actions/user/create.ts +++ b/common-ts/src/drift/base/actions/user/create.ts @@ -33,6 +33,11 @@ interface CreateUserAndDepositCollateralBaseIxsParams { fromSubAccountId?: number; customMaxMarginRatio?: number; delegate?: PublicKey; + /** + * Optional external wallet to deposit from. If provided, the deposit will be made + * from this wallet instead of the authority wallet. + */ + externalWallet?: PublicKey; } /** @@ -53,6 +58,7 @@ interface CreateUserAndDepositCollateralBaseIxsParams { * @param fromSubAccountId - Optional sub-account ID to transfer funds from * @param customMaxMarginRatio - Optional custom maximum margin ratio for the account * @param delegate - Optional delegate public key for the account. Immediately assigns this as the delegate of the account. + * @param externalWallet - Optional external wallet to deposit from (instead of authority wallet) * * @returns Promise resolving to an object containing: * - subAccountId: The ID of the newly created sub-account @@ -71,6 +77,7 @@ export const createUserAndDepositCollateralBaseIxs = async ({ fromSubAccountId, customMaxMarginRatio, delegate, + externalWallet, }: CreateUserAndDepositCollateralBaseIxsParams): Promise<{ subAccountId: number; userAccountPublicKey: PublicKey; @@ -86,10 +93,12 @@ export const createUserAndDepositCollateralBaseIxs = async ({ ? getTokenProgramForSpotMarket(spotMarketAccount) : undefined; + // Use external wallet for token address if provided, otherwise use authority + const depositSourceWallet = externalWallet ?? authority; const associatedDepositTokenAddressPromise = getTokenAddressForDepositAndWithdraw( spotMarketConfig.mint, - authority, + depositSourceWallet, tokenProgram ); const referrerNameAccountPromise: Promise = @@ -129,7 +138,6 @@ export const createUserAndDepositCollateralBaseIxs = async ({ } : undefined; - const ixs = []; const { ixs: createAndDepositIxs, userAccountPublicKey } = await driftClient.createInitializeUserAccountAndDepositCollateralIxs( amount, @@ -141,9 +149,10 @@ export const createUserAndDepositCollateralBaseIxs = async ({ referrerInfo, ZERO, customMaxMarginRatio, - poolId + poolId, + externalWallet ? { externalWallet } : undefined ); - ixs.push(...createAndDepositIxs); + const ixs: TransactionInstruction[] = [...createAndDepositIxs]; const nextSubAccountPublicKey = getUserAccountPublicKeySync( driftClient.program.programId, @@ -192,6 +201,7 @@ interface CreateUserAndDepositCollateralBaseTxnParams * @param fromSubAccountId - Optional sub-account ID to transfer funds from * @param customMaxMarginRatio - Optional custom maximum margin ratio for the account * @param txParams - Transaction parameters for building the transaction (compute units, priority fees, etc.) + * @param externalWallet - Optional external wallet to deposit from (instead of authority wallet) * * @returns Promise resolving to an object containing: * - transaction: The built transaction ready for signing (Transaction or VersionedTransaction) @@ -210,6 +220,7 @@ export const createUserAndDepositCollateralBaseTxn = async ({ fromSubAccountId, customMaxMarginRatio, txParams, + externalWallet, }: CreateUserAndDepositCollateralBaseTxnParams): Promise<{ transaction: Transaction | VersionedTransaction; userAccountPublicKey: PublicKey; @@ -227,6 +238,7 @@ export const createUserAndDepositCollateralBaseTxn = async ({ poolId, fromSubAccountId, customMaxMarginRatio, + externalWallet, }); const tx = await driftClient.buildTransaction(ixs, txParams); diff --git a/common-ts/src/drift/cli.ts b/common-ts/src/drift/cli.ts index d13c6253..a9067fe2 100644 --- a/common-ts/src/drift/cli.ts +++ b/common-ts/src/drift/cli.ts @@ -12,6 +12,7 @@ import { PRICE_PRECISION, MainnetSpotMarkets, DevnetSpotMarkets, + DriftEnv, } from '@drift-labs/sdk'; import { sign } from 'tweetnacl'; import { CentralServerDrift } from './Drift/clients/CentralServerDrift'; @@ -56,6 +57,7 @@ dotenv.config({ path: path.resolve(__dirname, '.env') }); * ts-node cli.ts swap --userAccount=11111111111111111111111111111111 --fromMarketIndex=1 --toMarketIndex=0 --fromAmount=1.5 --slippage=100 --swapMode=ExactIn * ts-node cli.ts swap --userAccount=11111111111111111111111111111111 --fromMarketIndex=1 --toMarketIndex=0 --toAmount=150 --slippage=100 --swapMode=ExactOut * ts-node cli.ts createUserAndDeposit --marketIndex=0 --amount=100 --accountName="Primary" + * ts-node cli.ts createUserAndDeposit --marketIndex=0 --amount=100 --fromWallet=22222222222222222222222222222222 --forAuthority=33333333333333333333333333333333 */ // Shared configuration @@ -242,10 +244,12 @@ async function initializeCentralServerDrift(): Promise { console.log(`✅ RPC Endpoint: ${process.env.ENDPOINT}\n`); // Initialize CentralServerDrift - console.log('🏗️ Initializing CentralServerDrift...'); + const driftEnv = (process.env.DRIFT_ENV as DriftEnv) ?? 'devnet'; + console.log(`🏗️ Initializing CentralServerDrift... (${driftEnv})`); + const rpcEndpoint = process.env.ENDPOINT as string; centralServerDrift = new CentralServerDrift({ - solanaRpcEndpoint: process.env.ENDPOINT as string, - driftEnv: 'mainnet-beta', // Change to 'devnet' for devnet testing + solanaRpcEndpoint: rpcEndpoint, + driftEnv, supportedPerpMarkets: [0, 1, 2], // SOL, BTC, ETH supportedSpotMarkets: [0, 1], // USDC, SOL additionalDriftClientConfig: { @@ -380,11 +384,20 @@ async function createUserAndDepositCommand(args: CliArgs): Promise { const customMaxMarginRatioArg = args.customMaxMarginRatio as | string | undefined; + const fromWallet = args.fromWallet as string | undefined; + const forAuthority = args.forAuthority as string | undefined; if (!marketIndexArg || !amountArg) { throw new Error('Required arguments: --marketIndex, --amount'); } + // If external wallet is provided, authority must also be specified + if (fromWallet && !forAuthority) { + throw new Error( + 'When using --fromWallet, you must also specify --forAuthority (the account owner)' + ); + } + const marketIndex = parseInt(marketIndexArg, 10); if (isNaN(marketIndex)) { throw new Error(`Invalid marketIndex: ${marketIndexArg}`); @@ -399,6 +412,7 @@ async function createUserAndDepositCommand(args: CliArgs): Promise { poolId?: number; fromSubAccountId?: number; customMaxMarginRatio?: number; + externalWallet?: PublicKey; } = {}; if (referrerName) { @@ -435,8 +449,17 @@ async function createUserAndDepositCommand(args: CliArgs): Promise { options.customMaxMarginRatio = customMaxMarginRatio; } + // Parse external wallet and authority if provided + const externalWallet = fromWallet ? new PublicKey(fromWallet) : undefined; + const authority = forAuthority + ? new PublicKey(forAuthority) + : wallet.publicKey; + if (externalWallet) { + options.externalWallet = externalWallet; + } + console.log('--- 🆕 Create User & Deposit Transaction ---'); - console.log(`🔑 Authority (wallet): ${wallet.publicKey.toString()}`); + console.log(`🔑 Authority (account owner): ${authority.toString()}`); console.log(`🏪 Spot Market Index: ${marketIndex}`); console.log( `💰 Initial Deposit: ${amountArg} (${amountBN.toString()} raw units)` @@ -456,11 +479,14 @@ async function createUserAndDepositCommand(args: CliArgs): Promise { if (options.customMaxMarginRatio !== undefined) { console.log(`📐 Custom Max Margin Ratio: ${options.customMaxMarginRatio}`); } + if (externalWallet) { + console.log(`💼 From External Wallet: ${externalWallet.toBase58()}`); + } const hasOptions = Object.keys(options).length > 0; const { transaction, userAccountPublicKey, subAccountId } = await centralServerDrift.getCreateAndDepositTxn( - wallet.publicKey, + authority, amountBN, marketIndex, hasOptions ? options : undefined @@ -938,11 +964,14 @@ function showUsage(): void { console.log('🆕 createUserAndDeposit'); console.log( - ' ts-node cli.ts createUserAndDeposit --marketIndex= --amount= [--accountName=] [--referrerName=] [--poolId=] [--fromSubAccountId=] [--customMaxMarginRatio=]' + ' ts-node cli.ts createUserAndDeposit --marketIndex= --amount= [--accountName=] [--referrerName=] [--poolId=] [--fromSubAccountId=] [--customMaxMarginRatio=] [--fromWallet= --forAuthority=]' ); console.log( ' Example: ts-node cli.ts createUserAndDeposit --marketIndex=0 --amount=100 --accountName="Primary"' ); + console.log( + ' Example (external wallet): ts-node cli.ts createUserAndDeposit --marketIndex=0 --amount=100 --fromWallet=22222222222222222222222222222222 --forAuthority=33333333333333333333333333333333' + ); console.log(''); console.log('💸 withdraw'); diff --git a/protocol b/protocol index fde9d608..b9407d24 160000 --- a/protocol +++ b/protocol @@ -1 +1 @@ -Subproject commit fde9d608810614e9f747e78bf3e5bf8bcbbe4f17 +Subproject commit b9407d243fd64ef78bb0f2bba9d3c503d8d7d56b