-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[js-legacy] Add scaled ui amount extension (#60)
- Loading branch information
1 parent
61658b9
commit 22a6437
Showing
9 changed files
with
316 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
clients/js-legacy/src/extensions/scaledUiAmount/actions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import type { ConfirmOptions, Connection, PublicKey, Signer, TransactionSignature } from '@solana/web3.js'; | ||
import { sendAndConfirmTransaction, Transaction } from '@solana/web3.js'; | ||
import { getSigners } from '../../actions/internal.js'; | ||
import { TOKEN_2022_PROGRAM_ID } from '../../constants.js'; | ||
import { createUpdateMultiplierDataInstruction } from './instructions.js'; | ||
|
||
/** | ||
* Update scaled UI amount multiplier | ||
* | ||
* @param connection Connection to use | ||
* @param payer Payer of the transaction fees | ||
* @param mint The token mint | ||
* @param owner Owner of the scaled UI amount mint | ||
* @param multiplier New multiplier | ||
* @param effectiveTimestamp Effective time stamp for the new multiplier | ||
* @param multiSigners Signing accounts if `owner` is a multisig | ||
* @param confirmOptions Options for confirming the transaction | ||
* @param programId SPL Token program account | ||
* | ||
* @return Signature of the confirmed transaction | ||
*/ | ||
export async function updateMultiplier( | ||
connection: Connection, | ||
payer: Signer, | ||
mint: PublicKey, | ||
owner: Signer | PublicKey, | ||
multiplier: number, | ||
effectiveTimestamp: bigint, | ||
multiSigners: Signer[] = [], | ||
confirmOptions?: ConfirmOptions, | ||
programId = TOKEN_2022_PROGRAM_ID, | ||
): Promise<TransactionSignature> { | ||
const [ownerPublicKey, signers] = getSigners(owner, multiSigners); | ||
|
||
const transaction = new Transaction().add( | ||
createUpdateMultiplierDataInstruction( | ||
mint, | ||
ownerPublicKey, | ||
multiplier, | ||
effectiveTimestamp, | ||
multiSigners, | ||
programId, | ||
), | ||
); | ||
|
||
return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './actions.js'; | ||
export * from './instructions.js'; | ||
export * from './state.js'; |
115 changes: 115 additions & 0 deletions
115
clients/js-legacy/src/extensions/scaledUiAmount/instructions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { struct, u8, f64 } from '@solana/buffer-layout'; | ||
import { publicKey, u64 } from '@solana/buffer-layout-utils'; | ||
import { TokenInstruction } from '../../instructions/types.js'; | ||
import type { Signer } from '@solana/web3.js'; | ||
import { TransactionInstruction, PublicKey } from '@solana/web3.js'; | ||
import { programSupportsExtensions, TOKEN_2022_PROGRAM_ID } from '../../constants.js'; | ||
import { TokenUnsupportedInstructionError } from '../../errors.js'; | ||
import { addSigners } from '../../instructions/internal.js'; | ||
|
||
export enum ScaledUiAmountInstruction { | ||
Initialize = 0, | ||
UpdateMultiplier = 1, | ||
} | ||
|
||
export interface InitializeScaledUiAmountConfigData { | ||
instruction: TokenInstruction.ScaledUiAmountExtension; | ||
scaledUiAmountInstruction: ScaledUiAmountInstruction.Initialize; | ||
authority: PublicKey | null; | ||
multiplier: number; | ||
} | ||
|
||
export const initializeScaledUiAmountConfigInstructionData = struct<InitializeScaledUiAmountConfigData>([ | ||
u8('instruction'), | ||
u8('scaledUiAmountInstruction'), | ||
publicKey('authority'), | ||
f64('multiplier'), | ||
]); | ||
|
||
/** | ||
* Construct an InitializeScaledUiAmountConfig instruction | ||
* | ||
* @param mint Token mint account | ||
* @param authority Optional authority that can update the multipliers | ||
* @param signers The signer account(s) | ||
* @param programId SPL Token program account | ||
* | ||
* @return Instruction to add to a transaction | ||
*/ | ||
export function createInitializeScaledUiAmountConfigInstruction( | ||
mint: PublicKey, | ||
authority: PublicKey | null, | ||
multiplier: number, | ||
programId: PublicKey = TOKEN_2022_PROGRAM_ID, | ||
): TransactionInstruction { | ||
if (!programSupportsExtensions(programId)) { | ||
throw new TokenUnsupportedInstructionError(); | ||
} | ||
const keys = [{ pubkey: mint, isSigner: false, isWritable: true }]; | ||
|
||
const data = Buffer.alloc(initializeScaledUiAmountConfigInstructionData.span); | ||
initializeScaledUiAmountConfigInstructionData.encode( | ||
{ | ||
instruction: TokenInstruction.ScaledUiAmountExtension, | ||
scaledUiAmountInstruction: ScaledUiAmountInstruction.Initialize, | ||
authority: authority ?? PublicKey.default, | ||
multiplier: multiplier, | ||
}, | ||
data, | ||
); | ||
|
||
return new TransactionInstruction({ keys, programId, data }); | ||
} | ||
|
||
export interface UpdateMultiplierData { | ||
instruction: TokenInstruction.ScaledUiAmountExtension; | ||
scaledUiAmountInstruction: ScaledUiAmountInstruction.UpdateMultiplier; | ||
multiplier: number; | ||
effectiveTimestamp: bigint; | ||
} | ||
|
||
export const updateMultiplierData = struct<UpdateMultiplierData>([ | ||
u8('instruction'), | ||
u8('scaledUiAmountInstruction'), | ||
f64('multiplier'), | ||
u64('effectiveTimestamp'), | ||
]); | ||
|
||
/** | ||
* Construct an UpdateMultiplierData instruction | ||
* | ||
* @param mint Token mint account | ||
* @param authority Optional authority that can update the multipliers | ||
* @param multiplier New multiplier | ||
* @param effectiveTimestamp Effective time stamp for the new multiplier | ||
* @param multiSigners Signing accounts if `owner` is a multisig | ||
* @param programId SPL Token program account | ||
* | ||
* @return Instruction to add to a transaction | ||
*/ | ||
export function createUpdateMultiplierDataInstruction( | ||
mint: PublicKey, | ||
authority: PublicKey, | ||
multiplier: number, | ||
effectiveTimestamp: bigint, | ||
multiSigners: (Signer | PublicKey)[] = [], | ||
programId: PublicKey = TOKEN_2022_PROGRAM_ID, | ||
): TransactionInstruction { | ||
if (!programSupportsExtensions(programId)) { | ||
throw new TokenUnsupportedInstructionError(); | ||
} | ||
const keys = addSigners([{ pubkey: mint, isSigner: false, isWritable: true }], authority, multiSigners); | ||
|
||
const data = Buffer.alloc(updateMultiplierData.span); | ||
updateMultiplierData.encode( | ||
{ | ||
instruction: TokenInstruction.ScaledUiAmountExtension, | ||
scaledUiAmountInstruction: ScaledUiAmountInstruction.UpdateMultiplier, | ||
multiplier, | ||
effectiveTimestamp, | ||
}, | ||
data, | ||
); | ||
|
||
return new TransactionInstruction({ keys, programId, data }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { f64, struct } from '@solana/buffer-layout'; | ||
import { publicKey, u64 } from '@solana/buffer-layout-utils'; | ||
import type { PublicKey } from '@solana/web3.js'; | ||
import type { Mint } from '../../state/mint.js'; | ||
import { ExtensionType, getExtensionData } from '../extensionType.js'; | ||
|
||
export interface ScaledUiAmountConfig { | ||
authority: PublicKey; | ||
multiplier: number; | ||
newMultiplierEffectiveTimestamp: bigint; | ||
newMultiplier: number; | ||
} | ||
|
||
export const ScaledUiAmountConfigLayout = struct<ScaledUiAmountConfig>([ | ||
publicKey('authority'), | ||
f64('multiplier'), | ||
u64('newMultiplierEffectiveTimestamp'), | ||
f64('newMultiplier'), | ||
]); | ||
|
||
export const SCALED_UI_AMOUNT_CONFIG_SIZE = ScaledUiAmountConfigLayout.span; | ||
|
||
export function getScaledUiAmountConfig(mint: Mint): ScaledUiAmountConfig | null { | ||
const extensionData = getExtensionData(ExtensionType.ScaledUiAmountConfig, mint.tlvData); | ||
if (extensionData !== null) { | ||
return ScaledUiAmountConfigLayout.decode(extensionData); | ||
} | ||
return null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { expect } from 'chai'; | ||
import type { Connection, Signer } from '@solana/web3.js'; | ||
import { PublicKey } from '@solana/web3.js'; | ||
import { Keypair, SystemProgram, Transaction, sendAndConfirmTransaction } from '@solana/web3.js'; | ||
import { TEST_PROGRAM_ID, newAccountWithLamports, getConnection } from '../common'; | ||
|
||
import { | ||
ExtensionType, | ||
createInitializeMintInstruction, | ||
createInitializeScaledUiAmountConfigInstruction, | ||
getMint, | ||
getMintLen, | ||
getScaledUiAmountConfig, | ||
updateMultiplier, | ||
setAuthority, | ||
AuthorityType, | ||
} from '../../src'; | ||
|
||
const TEST_TOKEN_DECIMALS = 2; | ||
const MINT_EXTENSIONS = [ExtensionType.ScaledUiAmountConfig]; | ||
|
||
describe('scaledUiAmount', () => { | ||
let connection: Connection; | ||
let payer: Signer; | ||
let owner: Keypair; | ||
let mint: PublicKey; | ||
let mintAuthority: Keypair; | ||
let multiplier: number; | ||
before(async () => { | ||
connection = await getConnection(); | ||
payer = await newAccountWithLamports(connection, 1000000000); | ||
owner = Keypair.generate(); | ||
multiplier = 5.0; | ||
}); | ||
|
||
beforeEach(async () => { | ||
const mintKeypair = Keypair.generate(); | ||
mint = mintKeypair.publicKey; | ||
mintAuthority = Keypair.generate(); | ||
const mintLen = getMintLen(MINT_EXTENSIONS); | ||
const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen); | ||
const mintTransaction = new Transaction().add( | ||
SystemProgram.createAccount({ | ||
fromPubkey: payer.publicKey, | ||
newAccountPubkey: mint, | ||
space: mintLen, | ||
lamports: mintLamports, | ||
programId: TEST_PROGRAM_ID, | ||
}), | ||
createInitializeScaledUiAmountConfigInstruction(mint, owner.publicKey, multiplier, TEST_PROGRAM_ID), | ||
createInitializeMintInstruction(mint, TEST_TOKEN_DECIMALS, mintAuthority.publicKey, null, TEST_PROGRAM_ID), | ||
); | ||
await sendAndConfirmTransaction(connection, mintTransaction, [payer, mintKeypair], undefined); | ||
}); | ||
|
||
it('initialize mint', async () => { | ||
const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); | ||
const scaledUiAmountConfig = getScaledUiAmountConfig(mintInfo); | ||
expect(scaledUiAmountConfig).to.not.equal(null); | ||
if (scaledUiAmountConfig !== null) { | ||
expect(scaledUiAmountConfig.authority).to.eql(owner.publicKey); | ||
expect(scaledUiAmountConfig.multiplier).to.eql(multiplier); | ||
} | ||
}); | ||
|
||
it('update authority', async () => { | ||
await setAuthority( | ||
connection, | ||
payer, | ||
mint, | ||
owner, | ||
AuthorityType.ScaledUiAmountConfig, | ||
null, | ||
[], | ||
undefined, | ||
TEST_PROGRAM_ID, | ||
); | ||
const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); | ||
const scaledUiAmountConfig = getScaledUiAmountConfig(mintInfo); | ||
expect(scaledUiAmountConfig).to.not.equal(null); | ||
if (scaledUiAmountConfig !== null) { | ||
expect(scaledUiAmountConfig.authority).to.eql(PublicKey.default); | ||
} | ||
}); | ||
|
||
it('update multiplier', async () => { | ||
const newMultiplier = 10.0; | ||
const effectiveTimestamp = BigInt(1000); | ||
|
||
await updateMultiplier( | ||
connection, | ||
payer, | ||
mint, | ||
owner, | ||
newMultiplier, | ||
effectiveTimestamp, | ||
[], | ||
undefined, | ||
TEST_PROGRAM_ID, | ||
); | ||
const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); | ||
const scaledUiAmountConfig = getScaledUiAmountConfig(mintInfo); | ||
expect(scaledUiAmountConfig).to.not.equal(null); | ||
if (scaledUiAmountConfig !== null) { | ||
expect(scaledUiAmountConfig.multiplier).to.eql(newMultiplier); | ||
expect(scaledUiAmountConfig.newMultiplierEffectiveTimestamp).to.eql(effectiveTimestamp); | ||
expect(scaledUiAmountConfig.newMultiplier).to.eql(newMultiplier); | ||
} | ||
}); | ||
}); |