-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Support interest bearing mint in token-js #3266
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import { | ||
| ConfirmOptions, | ||
| Connection, | ||
| Keypair, | ||
| PublicKey, | ||
| sendAndConfirmTransaction, | ||
| Signer, | ||
| SystemProgram, | ||
| Transaction, | ||
| } from '@solana/web3.js'; | ||
| import { getSigners } from '../../actions/internal'; | ||
| import { TOKEN_2022_PROGRAM_ID } from '../../constants'; | ||
| import { createInitializeMintInstruction } from '../../instructions'; | ||
| import { ExtensionType, getMintLen } from '../extensionType'; | ||
| import { | ||
| createInitializeInterestBearingMintInstruction, | ||
| createUpdateRateInterestBearingMintInstruction, | ||
| } from './instructions'; | ||
|
|
||
| /** | ||
| * Initialize an interest bearing account on a mint | ||
| * | ||
| * @param connection Connection to use | ||
| * @param payer Payer of the transaction fees | ||
| * @param mintAuthority Account or multisig that will control minting | ||
| * @param freezeAuthority Optional account or multisig that can freeze token accounts | ||
| * @param rateAuthority The public key for the account that can update the rate | ||
| * @param rate The initial interest rate | ||
| * @param decimals Location of the decimal place | ||
| * @param keypair Optional keypair, defaulting to a new random one | ||
| * @param confirmOptions Options for confirming the transaction | ||
| * @param programId SPL Token program account | ||
| * | ||
| * @return Public key of the mint | ||
| */ | ||
| export async function createInterestBearingMint( | ||
| connection: Connection, | ||
| payer: Signer, | ||
| mintAuthority: PublicKey, | ||
| freezeAuthority: PublicKey, | ||
| rateAuthority: PublicKey, | ||
| rate: number, | ||
| decimals: number, | ||
| keypair = Keypair.generate(), | ||
| confirmOptions?: ConfirmOptions, | ||
| programId = TOKEN_2022_PROGRAM_ID | ||
| ): Promise<PublicKey> { | ||
| const mintLen = getMintLen([ExtensionType.InterestBearingMint]); | ||
| const lamports = await connection.getMinimumBalanceForRentExemption(mintLen); | ||
| const transaction = new Transaction().add( | ||
| SystemProgram.createAccount({ | ||
| fromPubkey: payer.publicKey, | ||
| newAccountPubkey: keypair.publicKey, | ||
| space: mintLen, | ||
| lamports, | ||
| programId, | ||
| }), | ||
| createInitializeInterestBearingMintInstruction(keypair.publicKey, rateAuthority, rate, programId), | ||
| createInitializeMintInstruction(keypair.publicKey, decimals, mintAuthority, freezeAuthority, programId) | ||
| ); | ||
| await sendAndConfirmTransaction(connection, transaction, [payer, keypair], confirmOptions); | ||
| return keypair.publicKey; | ||
| } | ||
|
|
||
| /** | ||
| * Update the interest rate of an interest bearing account | ||
| * | ||
| * @param connection Connection to use | ||
| * @param payer Payer of the transaction fees | ||
| * @param mint Public key of the mint | ||
| * @param rateAuthority The public key for the account that can update the rate | ||
| * @param rate The initial interest rate | ||
| * @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 updateRateInterestBearingMint( | ||
| connection: Connection, | ||
| payer: Signer, | ||
| mint: PublicKey, | ||
| rateAuthority: Signer, | ||
| rate: number, | ||
| multiSigners: Signer[] = [], | ||
| confirmOptions?: ConfirmOptions, | ||
| programId = TOKEN_2022_PROGRAM_ID | ||
| ): Promise<string> { | ||
| const [rateAuthorityPublicKey, signers] = getSigners(rateAuthority, multiSigners); | ||
| const transaction = new Transaction().add( | ||
| createUpdateRateInterestBearingMintInstruction(mint, rateAuthorityPublicKey, rate, signers, programId) | ||
| ); | ||
|
|
||
| return await sendAndConfirmTransaction(connection, transaction, [payer, rateAuthority, ...signers], confirmOptions); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export * from './actions'; | ||
| export * from './instructions'; | ||
| export * from './state'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| import { struct, s16, u8 } from '@solana/buffer-layout'; | ||
| import { publicKey } from '@solana/buffer-layout-utils'; | ||
| import { PublicKey, Signer, TransactionInstruction } from '@solana/web3.js'; | ||
| import { TOKEN_2022_PROGRAM_ID } from '../../constants'; | ||
| import { TokenInstruction } from '../../instructions'; | ||
| import { addSigners } from '../../instructions/internal'; | ||
|
|
||
| export enum InterestBearingMintInstruction { | ||
| Initialize = 0, | ||
| UpdateRate = 1, | ||
| } | ||
|
|
||
| export interface InterestBearingMintInitializeInstructionData { | ||
| instruction: TokenInstruction.InterestBearingMintExtension; | ||
| interestBearingMintInstruction: InterestBearingMintInstruction.Initialize; | ||
| rateAuthority: PublicKey; | ||
| rate: number; | ||
| } | ||
|
|
||
| export interface InterestBearingMintUpdateRateInstructionData { | ||
| instruction: TokenInstruction.InterestBearingMintExtension; | ||
| interestBearingMintInstruction: InterestBearingMintInstruction.UpdateRate; | ||
| rate: number; | ||
| } | ||
|
|
||
| export const interestBearingMintInitializeInstructionData = struct<InterestBearingMintInitializeInstructionData>([ | ||
| u8('instruction'), | ||
| u8('interestBearingMintInstruction'), | ||
| // TODO: Make this an optional public key | ||
| publicKey('rateAuthority'), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm just realizing that this isn't done correctly with the transfer fee extension, since it's not actually a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gotcha. is that type typicall added to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case, we can probably add it to the token js library. If it eventually gets ported to the solana sdk, then we can also add it to buffer-layout-utils |
||
| s16('rate'), | ||
| ]); | ||
|
|
||
| export const interestBearingMintUpdateRateInstructionData = struct<InterestBearingMintUpdateRateInstructionData>([ | ||
| u8('instruction'), | ||
| u8('interestBearingMintInstruction'), | ||
| s16('rate'), | ||
| ]); | ||
|
|
||
| /** | ||
| * Construct an InitializeInterestBearingMint instruction | ||
| * | ||
| * @param mint Mint to initialize | ||
| * @param rateAuthority The public key for the account that can update the rate | ||
| * @param rate The initial interest rate | ||
| * @param programId SPL Token program account | ||
| * | ||
| * @return Instruction to add to a transaction | ||
| */ | ||
| export function createInitializeInterestBearingMintInstruction( | ||
| mint: PublicKey, | ||
| rateAuthority: PublicKey, | ||
| rate: number, | ||
| programId = TOKEN_2022_PROGRAM_ID | ||
| ) { | ||
| const keys = [{ pubkey: mint, isSigner: false, isWritable: true }]; | ||
| const data = Buffer.alloc(interestBearingMintInitializeInstructionData.span); | ||
| interestBearingMintInitializeInstructionData.encode( | ||
| { | ||
| instruction: TokenInstruction.InterestBearingMintExtension, | ||
| interestBearingMintInstruction: InterestBearingMintInstruction.Initialize, | ||
| rateAuthority, | ||
| rate, | ||
| }, | ||
| data | ||
| ); | ||
| return new TransactionInstruction({ keys, programId, data }); | ||
| } | ||
|
|
||
| /** | ||
| * Construct an UpdateRateInterestBearingMint instruction | ||
| * | ||
| * @param mint Mint to initialize | ||
| * @param rateAuthority The public key for the account that can update the rate | ||
| * @param rate The updated interest rate | ||
| * @param multiSigners Signing accounts if `rateAuthority` is a multisig | ||
| * @param programId SPL Token program account | ||
| * | ||
| * @return Instruction to add to a transaction | ||
| */ | ||
| export function createUpdateRateInterestBearingMintInstruction( | ||
| mint: PublicKey, | ||
| rateAuthority: PublicKey, | ||
| rate: number, | ||
| multiSigners: Signer[] = [], | ||
| programId = TOKEN_2022_PROGRAM_ID | ||
| ) { | ||
| const keys = addSigners( | ||
| [ | ||
| { pubkey: mint, isSigner: false, isWritable: true }, | ||
| { pubkey: rateAuthority, isSigner: !multiSigners.length, isWritable: false }, | ||
| ], | ||
| rateAuthority, | ||
| multiSigners | ||
| ); | ||
| const data = Buffer.alloc(interestBearingMintUpdateRateInstructionData.span); | ||
| interestBearingMintUpdateRateInstructionData.encode( | ||
| { | ||
| instruction: TokenInstruction.InterestBearingMintExtension, | ||
| interestBearingMintInstruction: InterestBearingMintInstruction.UpdateRate, | ||
| rate, | ||
| }, | ||
| data | ||
| ); | ||
| return new TransactionInstruction({ keys, programId, data }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { s16, ns64, struct } from '@solana/buffer-layout'; | ||
| import { publicKey } from '@solana/buffer-layout-utils'; | ||
| import { PublicKey } from '@solana/web3.js'; | ||
| import { Mint } from '../../state'; | ||
| import { ExtensionType, getExtensionData } from '../extensionType'; | ||
|
|
||
| export interface InterestBearingMintConfigState { | ||
| rateAuthority: PublicKey; | ||
| initializationTimestamp: BigInt; | ||
| preUpdateAverageRate: number; | ||
| lastUpdateTimestamp: BigInt; | ||
| currentRate: number; | ||
| } | ||
|
|
||
| export const InterestBearingMintConfigStateLayout = struct<InterestBearingMintConfigState>([ | ||
| publicKey('rateAuthority'), | ||
| ns64('initializationTimestamp'), | ||
| s16('preUpdateAverageRate'), | ||
| ns64('lastUpdateTimestamp'), | ||
| s16('currentRate'), | ||
| ]); | ||
|
|
||
| export const INTEREST_BEARING_MINT_CONFIG_STATE_SIZE = InterestBearingMintConfigStateLayout.span; | ||
|
|
||
| export function getInterestBearingMintConfigState(mint: Mint): InterestBearingMintConfigState | null { | ||
| const extensionData = getExtensionData(ExtensionType.InterestBearingMint, mint.tlvData); | ||
| if (extensionData !== null) { | ||
| return InterestBearingMintConfigStateLayout.decode(extensionData); | ||
| } | ||
| return null; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| import chai, { expect } from 'chai'; | ||
| import chaiAsPromised from 'chai-as-promised'; | ||
| chai.use(chaiAsPromised); | ||
|
|
||
| import { Connection, Keypair, PublicKey, Signer } from '@solana/web3.js'; | ||
| import { | ||
| createInterestBearingMint, | ||
| getInterestBearingMintConfigState, | ||
| getMint, | ||
| updateRateInterestBearingMint, | ||
| } from '../../src'; | ||
| import { getConnection, newAccountWithLamports, TEST_PROGRAM_ID } from '../common'; | ||
|
|
||
| const TEST_TOKEN_DECIMALS = 2; | ||
| const TEST_RATE = 10; | ||
| const TEST_UPDATE_RATE = 50; | ||
|
|
||
| describe('interestBearingMint', () => { | ||
| let connection: Connection; | ||
| let payer: Signer; | ||
| let mint: PublicKey; | ||
| let rateAuthority: Keypair; | ||
| let mintAuthority: Keypair; | ||
| let freezeAuthority: Keypair; | ||
| let mintKeypair: Keypair; | ||
|
|
||
| before(async () => { | ||
| connection = await getConnection(); | ||
| payer = await newAccountWithLamports(connection, 1000000000); | ||
| rateAuthority = Keypair.generate(); | ||
| mintAuthority = Keypair.generate(); | ||
| freezeAuthority = Keypair.generate(); | ||
| }); | ||
|
|
||
| it('initialize and update rate', async () => { | ||
| mintKeypair = Keypair.generate(); | ||
| mint = mintKeypair.publicKey; | ||
| await createInterestBearingMint( | ||
| connection, | ||
| payer, | ||
| mintAuthority.publicKey, | ||
| freezeAuthority.publicKey, | ||
| rateAuthority.publicKey, | ||
| TEST_RATE, | ||
| TEST_TOKEN_DECIMALS, | ||
| mintKeypair, | ||
| undefined, | ||
| TEST_PROGRAM_ID | ||
| ); | ||
| const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); | ||
| const interestBearingMintConfigState = getInterestBearingMintConfigState(mintInfo); | ||
| expect(interestBearingMintConfigState).to.not.be.null; | ||
| if (interestBearingMintConfigState !== null) { | ||
| expect(interestBearingMintConfigState.rateAuthority).to.eql(rateAuthority.publicKey); | ||
| expect(interestBearingMintConfigState.preUpdateAverageRate).to.eql(TEST_RATE); | ||
| expect(interestBearingMintConfigState.currentRate).to.eql(TEST_RATE); | ||
| expect(interestBearingMintConfigState.lastUpdateTimestamp).to.be.greaterThan(0); | ||
| expect(interestBearingMintConfigState.initializationTimestamp).to.be.greaterThan(0); | ||
| } | ||
|
|
||
| await updateRateInterestBearingMint( | ||
| connection, | ||
| payer, | ||
| mint, | ||
| rateAuthority, | ||
| TEST_UPDATE_RATE, | ||
| [], | ||
| undefined, | ||
| TEST_PROGRAM_ID | ||
| ); | ||
| const mintInfoUpdatedRate = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); | ||
| const updatedRateConfigState = getInterestBearingMintConfigState(mintInfoUpdatedRate); | ||
|
|
||
| expect(updatedRateConfigState).to.not.be.null; | ||
| if (updatedRateConfigState !== null) { | ||
| expect(updatedRateConfigState.rateAuthority).to.eql(rateAuthority.publicKey); | ||
| expect(updatedRateConfigState.currentRate).to.eql(TEST_UPDATE_RATE); | ||
| expect(updatedRateConfigState.preUpdateAverageRate).to.eql(TEST_RATE); | ||
| expect(updatedRateConfigState.lastUpdateTimestamp).to.be.greaterThan(0); | ||
| expect(updatedRateConfigState.initializationTimestamp).to.be.greaterThan(0); | ||
| } | ||
| }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.