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

[token-js] : Support for UiAmountToAmount and AmountToUiAmount instructions #3345

Merged
merged 5 commits into from
Aug 24, 2022
Merged
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
4 changes: 2 additions & 2 deletions token/js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions token/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"deploy:docs": "npm run docs && gh-pages --dist token/js --dotfiles"
},
"peerDependencies": {
"@solana/web3.js": "^1.20.0"
"@solana/web3.js": "^1.47.4"
jordaaash marked this conversation as resolved.
Show resolved Hide resolved
},
"dependencies": {
"@solana/buffer-layout": "^4.0.0",
Expand All @@ -58,7 +58,7 @@
},
"devDependencies": {
"@solana/spl-memo": "^0.2.1",
"@solana/web3.js": "^1.20.0",
"@solana/web3.js": "^1.47.4",
"@types/chai-as-promised": "^7.1.4",
"@types/chai": "^4.3.3",
"@types/mocha": "^9.1.0",
Expand Down
30 changes: 30 additions & 0 deletions token/js/src/actions/amountToUiAmount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Connection, PublicKey, Signer, TransactionError } from '@solana/web3.js';
import { Transaction } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from '../constants.js';
import { createAmountToUiAmountInstruction } from '../instructions/amountToUiAmount.js';

/**
* Amount as a string using mint-prescribed decimals
*
* @param connection Connection to use
* @param payer Payer of the transaction fees
* @param mint Mint for the account
* @param amount Amount of tokens to be converted to Ui Amount
* @param programId SPL Token program account
*
* @return Ui Amount generated
*/
export async function amountToUiAmount(
connection: Connection,
payer: Signer,
mint: PublicKey,
amount: number | bigint,
programId = TOKEN_PROGRAM_ID
): Promise<string | TransactionError | null> {
const transaction = new Transaction().add(createAmountToUiAmountInstruction(mint, amount, programId));
const { returnData, err } = (await connection.simulateTransaction(transaction, [payer], false)).value;
if (returnData?.data) {
return Buffer.from(returnData.data[0], returnData.data[1]).toString('utf-8');
}
return err;
}
31 changes: 16 additions & 15 deletions token/js/src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
export * from './amountToUiAmount.js';
export * from './approve.js';
export * from './approveChecked.js';
export * from './burn.js';
export * from './burnChecked.js';
export * from './closeAccount.js';
export * from './createAccount.js';
export * from './createAssociatedTokenAccount.js';
export * from './createMint.js';
export * from './createMultisig.js';
export * from './createNativeMint.js';
export * from './createAccount.js';
export * from './createWrappedNativeAccount.js';
export * from './createMultisig.js';
export * from './transfer.js';
export * from './approve.js';
export * from './freezeAccount.js';
export * from './getOrCreateAssociatedTokenAccount.js';
export * from './mintTo.js';
export * from './mintToChecked.js';
export * from './revoke.js';
export * from './setAuthority.js';
export * from './mintTo.js';
export * from './burn.js';
export * from './closeAccount.js';
export * from './freezeAccount.js';
export * from './syncNative.js';
export * from './thawAccount.js';
export * from './transfer.js';
export * from './transferChecked.js';
export * from './approveChecked.js';
export * from './mintToChecked.js';
export * from './burnChecked.js';
export * from './syncNative.js';

export * from './createAssociatedTokenAccount.js';
export * from './getOrCreateAssociatedTokenAccount.js';
export * from './uiAmountToAmount.js';
32 changes: 32 additions & 0 deletions token/js/src/actions/uiAmountToAmount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { u64 } from '@solana/buffer-layout-utils';
import type { Connection, PublicKey, Signer, TransactionError } from '@solana/web3.js';
import { Transaction } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from '../constants.js';
import { createUiAmountToAmountInstruction } from '../instructions/uiAmountToAmount.js';

/**
* Amount as a string using mint-prescribed decimals
*
* @param connection Connection to use
* @param payer Payer of the transaction fees
* @param mint Mint for the account
* @param amount Ui Amount of tokens to be converted to Amount
* @param programId SPL Token program account
*
* @return Ui Amount generated
*/
export async function uiAmountToAmount(
connection: Connection,
payer: Signer,
mint: PublicKey,
amount: string,
programId = TOKEN_PROGRAM_ID
): Promise<bigint | TransactionError | null> {
const transaction = new Transaction().add(createUiAmountToAmountInstruction(mint, amount, programId));
const { returnData, err } = (await connection.simulateTransaction(transaction, [payer], false)).value;
if (returnData) {
const data = Buffer.from(returnData.data[0], returnData.data[1]);
return u64().decode(data);
joncinque marked this conversation as resolved.
Show resolved Hide resolved
}
return err;
}
4 changes: 2 additions & 2 deletions token/js/src/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export * from './accountType.js';
export * from './defaultAccountState/index.js';
export * from './extensionType.js';
export * from './memoTransfer/index.js';
export * from './mintCloseAuthority.js';
export * from './immutableOwner.js';
export * from './interestBearingMint/index.js';
export * from './memoTransfer/index.js';
export * from './mintCloseAuthority.js';
export * from './nonTransferable.js';
export * from './transferFee/index.js';
6 changes: 3 additions & 3 deletions token/js/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from './extensions/index.js';
export * from './instructions/index.js';
export * from './state/index.js';
export * from './actions/index.js';
export * from './constants.js';
export * from './errors.js';
export * from './extensions/index.js';
export * from './instructions/index.js';
export * from './state/index.js';
128 changes: 128 additions & 0 deletions token/js/src/instructions/amountToUiAmount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { struct, u8 } from '@solana/buffer-layout';
import { u64 } from '@solana/buffer-layout-utils';
import type { AccountMeta, PublicKey } from '@solana/web3.js';
import { TransactionInstruction } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from '../constants.js';
import {
TokenInvalidInstructionDataError,
TokenInvalidInstructionKeysError,
TokenInvalidInstructionProgramError,
TokenInvalidInstructionTypeError,
} from '../errors.js';
import { TokenInstruction } from './types.js';

/** TODO: docs */
export interface AmountToUiAmountInstructionData {
instruction: TokenInstruction.AmountToUiAmount;
amount: bigint;
}

/** TODO: docs */
export const amountToUiAmountInstructionData = struct<AmountToUiAmountInstructionData>([
u8('instruction'),
u64('amount'),
]);

/**
* Construct a AmountToUiAmount instruction
*
* @param mint Public key of the mint
* @param amount Amount of tokens to be converted to UiAmount
* @param programId SPL Token program account
*
* @return Instruction to add to a transaction
*/
export function createAmountToUiAmountInstruction(
mint: PublicKey,
amount: number | bigint,
programId = TOKEN_PROGRAM_ID
): TransactionInstruction {
const keys = [{ pubkey: mint, isSigner: false, isWritable: false }];

const data = Buffer.alloc(amountToUiAmountInstructionData.span);
amountToUiAmountInstructionData.encode(
{
instruction: TokenInstruction.AmountToUiAmount,
amount: BigInt(amount),
},
data
);

return new TransactionInstruction({ keys, programId, data });
}

/** A decoded, valid AmountToUiAmount instruction */
export interface DecodedAmountToUiAmountInstruction {
programId: PublicKey;
keys: {
mint: AccountMeta;
};
data: {
instruction: TokenInstruction.AmountToUiAmount;
amount: bigint;
};
}

/**
* Decode a AmountToUiAmount instruction and validate it
*
* @param instruction Transaction instruction to decode
* @param programId SPL Token program account
*
* @return Decoded, valid instruction
*/
export function decodeAmountToUiAmountInstruction(
instruction: TransactionInstruction,
programId = TOKEN_PROGRAM_ID
): DecodedAmountToUiAmountInstruction {
if (!instruction.programId.equals(programId)) throw new TokenInvalidInstructionProgramError();
if (instruction.data.length !== amountToUiAmountInstructionData.span) throw new TokenInvalidInstructionDataError();

const {
keys: { mint },
data,
} = decodeAmountToUiAmountInstructionUnchecked(instruction);
if (data.instruction !== TokenInstruction.AmountToUiAmount) throw new TokenInvalidInstructionTypeError();
if (!mint) throw new TokenInvalidInstructionKeysError();

return {
programId,
keys: {
mint,
},
data,
};
}

/** A decoded, non-validated AmountToUiAmount instruction */
export interface DecodedAmountToUiAmountInstructionUnchecked {
programId: PublicKey;
keys: {
mint: AccountMeta | undefined;
};
data: {
instruction: number;
amount: bigint;
};
}

/**
* Decode a AmountToUiAmount instruction without validating it
*
* @param instruction Transaction instruction to decode
*
* @return Decoded, non-validated instruction
*/
export function decodeAmountToUiAmountInstructionUnchecked({
programId,
keys: [mint],
data,
}: TransactionInstruction): DecodedAmountToUiAmountInstructionUnchecked {
return {
programId,
keys: {
mint,
},
data: amountToUiAmountInstructionData.decode(data),
};
}
22 changes: 22 additions & 0 deletions token/js/src/instructions/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { u8 } from '@solana/buffer-layout';
import type { TransactionInstruction } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from '../constants.js';
import { TokenInvalidInstructionDataError, TokenInvalidInstructionTypeError } from '../errors.js';
import type { DecodedAmountToUiAmountInstruction } from './amountToUiAmount.js';
import { decodeAmountToUiAmountInstruction } from './amountToUiAmount.js';
import type { DecodedApproveInstruction } from './approve.js';
import { decodeApproveInstruction } from './approve.js';
import type { DecodedApproveCheckedInstruction } from './approveChecked.js';
Expand Down Expand Up @@ -41,6 +43,8 @@ import { decodeTransferInstruction } from './transfer.js';
import type { DecodedTransferCheckedInstruction } from './transferChecked.js';
import { decodeTransferCheckedInstruction } from './transferChecked.js';
import { TokenInstruction } from './types.js';
import type { DecodedUiAmountToAmountInstruction } from './uiAmountToAmount.js';
import { decodeUiAmountToAmountInstruction } from './uiAmountToAmount.js';

/** TODO: docs */
export type DecodedInstruction =
Expand All @@ -63,6 +67,8 @@ export type DecodedInstruction =
| DecodedInitializeAccount2Instruction
| DecodedSyncNativeInstruction
| DecodedInitializeAccount3Instruction
| DecodedAmountToUiAmountInstruction
| DecodedUiAmountToAmountInstruction
// | DecodedInitializeMultisig2Instruction
// | DecodedInitializeMint2Instruction
// TODO: implement ^ and remove `never`
Expand Down Expand Up @@ -96,6 +102,8 @@ export function decodeInstruction(
if (type === TokenInstruction.InitializeAccount2)
return decodeInitializeAccount2Instruction(instruction, programId);
if (type === TokenInstruction.SyncNative) return decodeSyncNativeInstruction(instruction, programId);
if (type === TokenInstruction.AmountToUiAmount) return decodeAmountToUiAmountInstruction(instruction, programId);
if (type === TokenInstruction.UiAmountToAmount) return decodeUiAmountToAmountInstruction(instruction, programId);
// TODO: implement
if (type === TokenInstruction.InitializeAccount3)
return decodeInitializeAccount3Instruction(instruction, programId);
Expand Down Expand Up @@ -212,6 +220,20 @@ export function isInitializeAccount3Instruction(
return decoded.data.instruction === TokenInstruction.InitializeAccount3;
}

/** TODO: docs */
export function isAmountToUiAmountInstruction(
decoded: DecodedInstruction
): decoded is DecodedAmountToUiAmountInstruction {
return decoded.data.instruction === TokenInstruction.AmountToUiAmount;
}

/** TODO: docs */
export function isUiamountToAmountInstruction(
decoded: DecodedInstruction
): decoded is DecodedUiAmountToAmountInstruction {
return decoded.data.instruction === TokenInstruction.UiAmountToAmount;
}

/** TODO: docs, implement */
// export function isInitializeMultisig2Instruction(
// decoded: DecodedInstruction
Expand Down
4 changes: 3 additions & 1 deletion token/js/src/instructions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export * from './initializeAccount3.js'; // 18
export * from './initializeMultisig2.js'; // 19
export * from './initializeMint2.js'; // 20
export * from './initializeImmutableOwner.js'; // 22
export * from './initializeMintCloseAuthority.js'; // 23
export * from './amountToUiAmount.js'; // 23
export * from './uiAmountToAmount.js'; // 24
export * from './initializeMintCloseAuthority.js'; // 25
export * from './reallocate.js'; // 29
export * from './createNativeMint.js'; // 31
export * from './initializeNonTransferableMint.js'; // 32
Loading