Skip to content
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

added withdraw excess lamports instruction #39

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clients/js/src/generated/instructions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,6 @@ export * from './updateTokenGroupMaxSize';
export * from './updateTokenGroupUpdateAuthority';
export * from './updateTokenMetadataField';
export * from './updateTokenMetadataUpdateAuthority';
export * from './withdrawExcessLamports';
export * from './withdrawWithheldTokensFromAccounts';
export * from './withdrawWithheldTokensFromMint';
217 changes: 217 additions & 0 deletions clients/js/src/generated/instructions/withdrawExcessLamports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/**
* This code was AUTOGENERATED using the codama library.
* Please DO NOT EDIT THIS FILE, instead use visitors
* to add features, then rerun codama to update it.
*
* @see https://github.com/codama-idl/codama
*/

import {
AccountRole,
combineCodec,
getStructDecoder,
getStructEncoder,
getU8Decoder,
getU8Encoder,
transformEncoder,
type Address,
type Codec,
type Decoder,
type Encoder,
type IAccountMeta,
type IAccountSignerMeta,
type IInstruction,
type IInstructionWithAccounts,
type IInstructionWithData,
type ReadonlySignerAccount,
type TransactionSigner,
type WritableAccount,
} from '@solana/web3.js';
import { TOKEN_2022_PROGRAM_ADDRESS } from '../programs';
import { getAccountMetaFactory, type ResolvedAccount } from '../shared';

export const WITHDRAW_EXCESS_LAMPORTS_DISCRIMINATOR = 38;

export function getWithdrawExcessLamportsDiscriminatorBytes() {
return getU8Encoder().encode(WITHDRAW_EXCESS_LAMPORTS_DISCRIMINATOR);
}

export type WithdrawExcessLamportsInstruction<
TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS,
TAccountProgramAccount extends string | IAccountMeta<string> = string,
TAccountDestinationAccount extends string | IAccountMeta<string> = string,
TAccountAuthority extends string | IAccountMeta<string> = string,
TRemainingAccounts extends readonly IAccountMeta<string>[] = [],
> = IInstruction<TProgram> &
IInstructionWithData<Uint8Array> &
IInstructionWithAccounts<
[
TAccountProgramAccount extends string
? WritableAccount<TAccountProgramAccount>
: TAccountProgramAccount,
TAccountDestinationAccount extends string
? WritableAccount<TAccountDestinationAccount>
: TAccountDestinationAccount,
TAccountAuthority extends string
? ReadonlySignerAccount<TAccountAuthority> &
IAccountSignerMeta<TAccountAuthority>
: TAccountAuthority,
...TRemainingAccounts,
]
>;

export type WithdrawExcessLamportsInstructionData = { discriminator: number };

export type WithdrawExcessLamportsInstructionDataArgs = {};

export function getWithdrawExcessLamportsInstructionDataEncoder(): Encoder<WithdrawExcessLamportsInstructionDataArgs> {
return transformEncoder(
getStructEncoder([['discriminator', getU8Encoder()]]),
(value) => ({
...value,
discriminator: WITHDRAW_EXCESS_LAMPORTS_DISCRIMINATOR,
})
);
}

export function getWithdrawExcessLamportsInstructionDataDecoder(): Decoder<WithdrawExcessLamportsInstructionData> {
return getStructDecoder([['discriminator', getU8Decoder()]]);
}

export function getWithdrawExcessLamportsInstructionDataCodec(): Codec<
WithdrawExcessLamportsInstructionDataArgs,
WithdrawExcessLamportsInstructionData
> {
return combineCodec(
getWithdrawExcessLamportsInstructionDataEncoder(),
getWithdrawExcessLamportsInstructionDataDecoder()
);
}

export type WithdrawExcessLamportsInput<
TAccountProgramAccount extends string = string,
TAccountDestinationAccount extends string = string,
TAccountAuthority extends string = string,
> = {
/** The program account from which lamports will be withdrawn. */
programAccount: Address<TAccountProgramAccount>;
/** The account to which the excess lamports will be transferred. */
destinationAccount: Address<TAccountDestinationAccount>;
/** The authority authorized to withdraw lamports from the program account. */
authority: TransactionSigner<TAccountAuthority>;
multiSigners?: Array<TransactionSigner>;
};

export function getWithdrawExcessLamportsInstruction<
TAccountProgramAccount extends string,
TAccountDestinationAccount extends string,
TAccountAuthority extends string,
TProgramAddress extends Address = typeof TOKEN_2022_PROGRAM_ADDRESS,
>(
input: WithdrawExcessLamportsInput<
TAccountProgramAccount,
TAccountDestinationAccount,
TAccountAuthority
>,
config?: { programAddress?: TProgramAddress }
): WithdrawExcessLamportsInstruction<
TProgramAddress,
TAccountProgramAccount,
TAccountDestinationAccount,
TAccountAuthority
> {
// Program address.
const programAddress = config?.programAddress ?? TOKEN_2022_PROGRAM_ADDRESS;

// Original accounts.
const originalAccounts = {
programAccount: { value: input.programAccount ?? null, isWritable: true },
destinationAccount: {
value: input.destinationAccount ?? null,
isWritable: true,
},
authority: { value: input.authority ?? null, isWritable: false },
};
const accounts = originalAccounts as Record<
keyof typeof originalAccounts,
ResolvedAccount
>;

// Original args.
const args = { ...input };

// Remaining accounts.
const remainingAccounts: IAccountMeta[] = (args.multiSigners ?? []).map(
(signer) => ({
address: signer.address,
role: AccountRole.READONLY_SIGNER,
signer,
})
);

const getAccountMeta = getAccountMetaFactory(programAddress, 'programId');
const instruction = {
accounts: [
getAccountMeta(accounts.programAccount),
getAccountMeta(accounts.destinationAccount),
getAccountMeta(accounts.authority),
...remainingAccounts,
],
programAddress,
data: getWithdrawExcessLamportsInstructionDataEncoder().encode({}),
} as WithdrawExcessLamportsInstruction<
TProgramAddress,
TAccountProgramAccount,
TAccountDestinationAccount,
TAccountAuthority
>;

return instruction;
}

export type ParsedWithdrawExcessLamportsInstruction<
TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS,
TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[],
> = {
programAddress: Address<TProgram>;
accounts: {
/** The program account from which lamports will be withdrawn. */
programAccount: TAccountMetas[0];
/** The account to which the excess lamports will be transferred. */
destinationAccount: TAccountMetas[1];
/** The authority authorized to withdraw lamports from the program account. */
authority: TAccountMetas[2];
};
data: WithdrawExcessLamportsInstructionData;
};

export function parseWithdrawExcessLamportsInstruction<
TProgram extends string,
TAccountMetas extends readonly IAccountMeta[],
>(
instruction: IInstruction<TProgram> &
IInstructionWithAccounts<TAccountMetas> &
IInstructionWithData<Uint8Array>
): ParsedWithdrawExcessLamportsInstruction<TProgram, TAccountMetas> {
if (instruction.accounts.length < 3) {
// TODO: Coded error.
throw new Error('Not enough accounts');
}
let accountIndex = 0;
const getNextAccount = () => {
const accountMeta = instruction.accounts![accountIndex]!;
accountIndex += 1;
return accountMeta;
};
return {
programAddress: instruction.programAddress,
accounts: {
programAccount: getNextAccount(),
destinationAccount: getNextAccount(),
authority: getNextAccount(),
},
data: getWithdrawExcessLamportsInstructionDataDecoder().decode(
instruction.data
),
};
}
8 changes: 8 additions & 0 deletions clients/js/src/generated/programs/token2022.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
type ParsedUpdateTokenGroupUpdateAuthorityInstruction,
type ParsedUpdateTokenMetadataFieldInstruction,
type ParsedUpdateTokenMetadataUpdateAuthorityInstruction,
type ParsedWithdrawExcessLamportsInstruction,
type ParsedWithdrawWithheldTokensFromAccountsInstruction,
type ParsedWithdrawWithheldTokensFromMintInstruction,
} from '../instructions';
Expand Down Expand Up @@ -160,6 +161,7 @@ export enum Token2022Instruction {
Reallocate,
EnableMemoTransfers,
DisableMemoTransfers,
WithdrawExcessLamports,
InitializeMetadataPointer,
UpdateMetadataPointer,
InitializeGroupPointer,
Expand Down Expand Up @@ -406,6 +408,9 @@ export function identifyToken2022Instruction(
) {
return Token2022Instruction.DisableMemoTransfers;
}
if (containsBytes(data, getU8Encoder().encode(38), 0)) {
return Token2022Instruction.WithdrawExcessLamports;
}
if (
containsBytes(data, getU8Encoder().encode(39), 0) &&
containsBytes(data, getU8Encoder().encode(0), 1)
Expand Down Expand Up @@ -676,6 +681,9 @@ export type ParsedToken2022Instruction<
| ({
instructionType: Token2022Instruction.DisableMemoTransfers;
} & ParsedDisableMemoTransfersInstruction<TProgram>)
| ({
instructionType: Token2022Instruction.WithdrawExcessLamports;
} & ParsedWithdrawExcessLamportsInstruction<TProgram>)
| ({
instructionType: Token2022Instruction.InitializeMetadataPointer;
} & ParsedInitializeMetadataPointerInstruction<TProgram>)
Expand Down
58 changes: 58 additions & 0 deletions clients/js/test/withdrawExcessLamports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import test from 'ava';
import { generateKeyPairSigner } from '@solana/web3.js';
import {
createDefaultSolanaClient,
sendAndConfirmInstructions,
generateKeyPairSignerWithSol,
} from './_setup';
import { getWithdrawExcessLamportsInstruction } from '../src';

test('it withdraws excess lamports from program account', async (t) => {
// Setup Solana client and accounts
const client = createDefaultSolanaClient();

// Step 1: Generate the required accounts and fund the program account with excess lamports
const [programAccount, destinationAccount, authority] = await Promise.all([
generateKeyPairSignerWithSol(client, 2_000_000_000n), // Fund with 2 SOL (excess)
generateKeyPairSignerWithSol(client), // Empty destination account
generateKeyPairSigner(), // Authority
]);

// Step 2: Check initial balances and log them
const initialProgramBalance = await client.rpc.getBalance(programAccount.address).send();
const initialDestinationBalance = await client.rpc.getBalance(destinationAccount.address).send();

// Step 3: Define the required minimum balance
const requiredBalance = 1_000_000n; // Assuming 1_000_000 lamports as the minimum required balance

// Step 4: Prepare the withdraw excess lamports instruction
const withdrawInstruction = getWithdrawExcessLamportsInstruction({
programAccount: programAccount.address,
destinationAccount: destinationAccount.address,
authority: authority,
});

// Step 5: Attempt to send the transaction and catch any errors for better debugging
try {
await sendAndConfirmInstructions(client, authority, [withdrawInstruction]);
} catch (error) {
console.error("Transaction failed: ", error);
t.fail("Failed to withdraw excess lamports.");
return;
}

// Step 6: Fetch final balances and validate
const finalProgramBalance = await client.rpc.getBalance(programAccount.address).send();
const finalDestinationBalance = await client.rpc.getBalance(destinationAccount.address).send();

// Ensure program account retains only the required minimum balance
t.is(Number(finalProgramBalance.value), Number(requiredBalance), "Program account balance mismatch");

// Calculate expected transfer amount and verify it in the destination balance
const expectedTransferAmount = initialProgramBalance.value - requiredBalance;
t.is(
Number(finalDestinationBalance.value) - Number(initialDestinationBalance.value),
Number(expectedTransferAmount),
"Transferred amount does not match expected excess lamports"
);
});
Loading
Loading