Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 35df63b

Browse files
committed
working with sys clock
1 parent 83cf986 commit 35df63b

File tree

4 files changed

+195
-87
lines changed

4 files changed

+195
-87
lines changed

pnpm-lock.yaml

Lines changed: 62 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

token/js/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@
6161
},
6262
"devDependencies": {
6363
"@solana/codecs-strings": "2.0.0",
64+
"@solana/rpc-types": "^2.0.0",
6465
"@solana/spl-memo": "0.2.4",
66+
"@solana/sysvars": "^2.0.0",
6567
"@solana/web3.js": "^1.95.5",
6668
"@types/chai": "^5.0.1",
6769
"@types/chai-as-promised": "^8.0.1",

token/js/src/actions/amountToUiAmount.ts

Lines changed: 58 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import Decimal from 'decimal.js';
2-
import type { Connection, PublicKey, Signer, TransactionError } from '@solana/web3.js';
3-
import { Transaction } from '@solana/web3.js';
4-
import { TOKEN_PROGRAM_ID } from '../constants.js';
2+
import type { Connection, Signer, TransactionError } from '@solana/web3.js';
3+
import { PublicKey, Transaction } from '@solana/web3.js';
4+
import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../constants.js';
55
import { createAmountToUiAmountInstruction } from '../instructions/amountToUiAmount.js';
6-
import { getMint } from '../state/mint.js';
6+
import { getMint, unpackMint } from '../state/mint.js';
77
import { getInterestBearingMintConfigState } from '../extensions/interestBearingMint/state.js';
88

99
/**
@@ -32,8 +32,14 @@ export async function amountToUiAmount(
3232
return err;
3333
}
3434

35-
const ONE_IN_BASIS_POINTS = 10000;
36-
const SECONDS_PER_YEAR = 60 * 60 * 24 * 365.24;
35+
const calculateExponentForTimesAndRate = (t1: number, t2: number, r: number) => {
36+
const ONE_IN_BASIS_POINTS = 10000;
37+
const SECONDS_PER_YEAR = 60 * 60 * 24 * 365.24;
38+
const timespan = new Decimal(t2).minus(t1);
39+
const numerator = new Decimal(r).times(timespan);
40+
const exponent = numerator.div(new Decimal(SECONDS_PER_YEAR).times(ONE_IN_BASIS_POINTS));
41+
return exponent.exp();
42+
}
3743

3844
/**
3945
* Convert amount to UiAmount for a mint with interest bearing extension without simulating a transaction
@@ -61,9 +67,8 @@ const SECONDS_PER_YEAR = 60 * 60 * 24 * 365.24;
6167
*
6268
* @return Amount scaled by accrued interest as a string with appropriate decimal places
6369
*/
64-
6570
export function amountToUiAmountWithoutSimulation(
66-
amount: string,
71+
amount: bigint,
6772
decimals: number,
6873
currentTimestamp: number, // in seconds
6974
lastUpdateTimestamp: number,
@@ -75,31 +80,38 @@ export function amountToUiAmountWithoutSimulation(
7580

7681
// Calculate pre-update exponent
7782
// e^(preUpdateAverageRate * (lastUpdateTimestamp - initializationTimestamp) / (SECONDS_PER_YEAR * ONE_IN_BASIS_POINTS))
78-
const preUpdateTimespan = new Decimal(lastUpdateTimestamp).minus(initializationTimestamp);
79-
const preUpdateNumerator = new Decimal(preUpdateAverageRate).times(preUpdateTimespan);
80-
const preUpdateExponent = preUpdateNumerator.div(new Decimal(SECONDS_PER_YEAR).times(ONE_IN_BASIS_POINTS));
81-
const preUpdateExp = preUpdateExponent.exp();
83+
const preUpdateExp = calculateExponentForTimesAndRate(initializationTimestamp, lastUpdateTimestamp, preUpdateAverageRate)
8284

8385
// Calculate post-update exponent
8486
// e^(currentRate * (currentTimestamp - lastUpdateTimestamp) / (SECONDS_PER_YEAR * ONE_IN_BASIS_POINTS))
85-
const postUpdateTimespan = new Decimal(currentTimestamp).minus(lastUpdateTimestamp);
86-
const postUpdateNumerator = new Decimal(currentRate).times(postUpdateTimespan);
87-
const postUpdateExponent = postUpdateNumerator.div(new Decimal(SECONDS_PER_YEAR).times(ONE_IN_BASIS_POINTS));
88-
const postUpdateExp = postUpdateExponent.exp();
87+
const postUpdateExp = calculateExponentForTimesAndRate(lastUpdateTimestamp, Number(currentTimestamp), currentRate)
8988

9089
// Calculate total scale
9190
const totalScale = preUpdateExp.times(postUpdateExp);
9291

9392
// Calculate scaled amount with interest rounded down to the nearest unit
9493
const decimalsFactor = new Decimal(10).pow(decimals);
95-
const scaledAmountWithInterest = new Decimal(amount).times(totalScale).div(decimalsFactor).toDecimalPlaces(decimals, Decimal.ROUND_DOWN);
94+
const scaledAmountWithInterest = new Decimal(amount.toString()).times(totalScale).div(decimalsFactor).toDecimalPlaces(decimals, Decimal.ROUND_DOWN);
9695

9796
return scaledAmountWithInterest.toString();
9897
}
9998

99+
const getSysvarClockTimestamp = async (connection: Connection): Promise<number> => {
100+
const info = await connection.getParsedAccountInfo(new PublicKey('SysvarC1ock11111111111111111111111111111111'));
101+
if (!info) {
102+
throw new Error('Failed to fetch sysvar clock');
103+
}
104+
if (typeof info.value === 'object' && info.value && 'data' in info.value && 'parsed' in info.value.data) {
105+
return info.value.data.parsed.info.unixTimestamp;
106+
}
107+
throw new Error('Failed to parse sysvar clock');
108+
}
109+
110+
100111
/**
101-
* Convert amount to UiAmount for a mint with interest bearing extension without simulating a transaction
102-
* This implements the same logic as the CPI instruction available in /token/program-2022/src/extension/interest_bearing_mint/mod.rs
112+
* Convert amount to UiAmount for a mint without simulating a transaction
113+
* This implements the same logic as `process_amount_to_ui_amount` in /token/program-2022/src/processor.rs
114+
* and `process_amount_to_ui_amount` in /token/program/src/processor.rs
103115
*
104116
* @param connection Connection to use
105117
* @param mint Mint to use for calculations
@@ -111,29 +123,30 @@ export function amountToUiAmountWithoutSimulation(
111123
export async function amountToUiAmountForMintWithoutSimulation(
112124
connection: Connection,
113125
mint: PublicKey,
114-
amount: string,
115-
programId = TOKEN_PROGRAM_ID,
126+
amount: bigint,
116127
): Promise<string> {
117128
Decimal.set({ toExpPos: 24, toExpNeg: -24 })
118-
const mintInfo = await getMint(connection, mint, 'confirmed', programId);
119-
const amountDecimal = new Decimal(amount.toString());
120-
const decimalsFactor = new Decimal(10).pow(mintInfo.decimals);
121-
122-
if (programId.equals(TOKEN_PROGRAM_ID)) {
123-
console.log('amountDecimal', amountDecimal.toString(), 'mintInfo', mintInfo);
124-
return amountDecimal.div(decimalsFactor).toString();
129+
const accountInfo = await connection.getAccountInfo(mint);
130+
const programId = accountInfo?.owner;
131+
if (programId !== TOKEN_PROGRAM_ID && programId !== TOKEN_2022_PROGRAM_ID) {
132+
throw new Error('Invalid program ID');
125133
}
126134

135+
const mintInfo = unpackMint(mint, accountInfo, programId);
136+
127137
const interestBearingMintConfigState = getInterestBearingMintConfigState(mintInfo);
128138
if (!interestBearingMintConfigState) {
139+
const amountDecimal = new Decimal(amount.toString());
140+
const decimalsFactor = new Decimal(10).pow(mintInfo.decimals);
129141
return amountDecimal.div(decimalsFactor).toString();
130142
}
131143

132-
const currentTime = Math.floor(Date.now() / 1000); // Convert to seconds
144+
const timestamp = await getSysvarClockTimestamp(connection);
145+
133146
return amountToUiAmountWithoutSimulation(
134147
amount,
135148
mintInfo.decimals,
136-
currentTime,
149+
timestamp,
137150
interestBearingMintConfigState.lastUpdateTimestamp,
138151
interestBearingMintConfigState.initializationTimestamp,
139152
interestBearingMintConfigState.preUpdateAverageRate,
@@ -169,7 +182,6 @@ export async function amountToUiAmountForMintWithoutSimulation(
169182
*
170183
* @return Original amount (principle) without interest
171184
*/
172-
173185
export function uiAmountToAmountWithoutSimulation(
174186
uiAmount: string,
175187
decimals: number,
@@ -185,16 +197,10 @@ export function uiAmountToAmountWithoutSimulation(
185197
const uiAmountScaled = uiAmountDecimal.mul(decimalsFactor);
186198

187199
// Calculate pre-update exponent
188-
const preUpdateTimespan = new Decimal(lastUpdateTimestamp).minus(initializationTimestamp);
189-
const preUpdateNumerator = new Decimal(preUpdateAverageRate).times(preUpdateTimespan);
190-
const preUpdateExponent = preUpdateNumerator.div(new Decimal(SECONDS_PER_YEAR).times(ONE_IN_BASIS_POINTS));
191-
const preUpdateExp = preUpdateExponent.exp();
200+
const preUpdateExp = calculateExponentForTimesAndRate(initializationTimestamp, lastUpdateTimestamp, preUpdateAverageRate);
192201

193202
// Calculate post-update exponent
194-
const postUpdateTimespan = new Decimal(currentTimestamp).minus(lastUpdateTimestamp);
195-
const postUpdateNumerator = new Decimal(currentRate).times(postUpdateTimespan);
196-
const postUpdateExponent = postUpdateNumerator.div(new Decimal(SECONDS_PER_YEAR).times(ONE_IN_BASIS_POINTS));
197-
const postUpdateExp = postUpdateExponent.exp();
203+
const postUpdateExp = calculateExponentForTimesAndRate(lastUpdateTimestamp, currentTimestamp, currentRate);
198204

199205
// Calculate total scale
200206
const totalScale = preUpdateExp.times(postUpdateExp);
@@ -205,40 +211,42 @@ export function uiAmountToAmountWithoutSimulation(
205211
}
206212

207213
/**
208-
* Convert a UI amount with interest back to the original UI amount without interest
214+
* Convert a UI amount back to the raw amount
209215
*
210216
* @param connection Connection to use
211217
* @param mint Mint to use for calculations
212-
* @param uiAmount UI Amount (principle plus continuously compounding interest) to be converted back to original principle
218+
* @param uiAmount UI Amount to be converted back to raw amount
213219
* @param programId SPL Token program account (default: TOKEN_PROGRAM_ID)
214220
*
215221
*
216-
* @return Original UI Amount (principle) without interest
222+
* @return Raw amount
217223
*/
218224
export async function uiAmountToAmountForMintWithoutSimulation(
219225
connection: Connection,
220226
mint: PublicKey,
221227
uiAmount: string,
222-
programId = TOKEN_PROGRAM_ID,
223228
): Promise<bigint> {
224229
Decimal.set({ toExpPos: 24, toExpNeg: -24 })
225-
const mintInfo = await getMint(connection, mint, 'confirmed', programId);
226-
const uiAmountScaled = new Decimal(uiAmount).mul(new Decimal(10).pow(mintInfo.decimals));
227-
228-
if (programId.equals(TOKEN_PROGRAM_ID)) {
229-
return BigInt(uiAmountScaled.trunc().toString());
230+
const accountInfo = await connection.getAccountInfo(mint);
231+
const programId = accountInfo?.owner;
232+
if (programId !== TOKEN_PROGRAM_ID && programId !== TOKEN_2022_PROGRAM_ID) {
233+
throw new Error('Invalid program ID');
230234
}
231235

236+
const mintInfo = await getMint(connection, mint, 'confirmed', programId);
237+
232238
const interestBearingMintConfigState = getInterestBearingMintConfigState(mintInfo);
233239
if (!interestBearingMintConfigState) {
240+
const uiAmountScaled = new Decimal(uiAmount).mul(new Decimal(10).pow(mintInfo.decimals));
234241
return BigInt(uiAmountScaled.trunc().toString());
235242
}
236243

237-
const currentTime = Math.floor(Date.now() / 1000); // Convert to seconds
244+
const timestamp = await getSysvarClockTimestamp(connection);
245+
238246
return uiAmountToAmountWithoutSimulation(
239247
uiAmount,
240248
mintInfo.decimals,
241-
currentTime,
249+
timestamp,
242250
interestBearingMintConfigState.lastUpdateTimestamp,
243251
interestBearingMintConfigState.initializationTimestamp,
244252
interestBearingMintConfigState.preUpdateAverageRate,

0 commit comments

Comments
 (0)