diff --git a/packages/walletkit/GLOBALOPTIONS.md b/packages/walletkit/GLOBALOPTIONS.md new file mode 100644 index 000000000..0e7d1ad98 --- /dev/null +++ b/packages/walletkit/GLOBALOPTIONS.md @@ -0,0 +1,37 @@ +# KitGlobalOptions + +`KitGlobalOptions` is a static class for global WalletKit configuration that affects all instances. Currently provides time synchronization functionality, with more global settings planned for future releases. + +## Time Provider + +Configure how WalletKit obtains current time for transaction validation. Useful for avoiding clock skew issues and ensuring accurate `validUntil` validation. + +### API + +```typescript +class KitGlobalOptions { + static setGetCurrentTime(fn: () => Promise | number): void; + static getCurrentTime(): Promise; +} +``` + +### Usage + +```typescript +import { KitGlobalOptions } from '@ton/walletkit'; + +// Set custom time provider (optional, before creating TonWalletKit) +KitGlobalOptions.setGetCurrentTime(async () => { + const response = await fetch('https://your-api.com/time'); + const { timestamp } = await response.json(); + return timestamp; // Unix timestamp in seconds +}); +``` + +**Default behavior**: Uses `Math.floor(Date.now() / 1000)` if not configured. + +## Notes + +- **Global scope**: Affects all `TonWalletKit` instances +- **Time format**: Unix timestamp in seconds (not milliseconds) +- **Set once**: Configure at app initialization diff --git a/packages/walletkit/README.md b/packages/walletkit/README.md index 15c3c4cb1..825f084a0 100644 --- a/packages/walletkit/README.md +++ b/packages/walletkit/README.md @@ -24,6 +24,7 @@ A production-ready wallet-side integration layer for TON Connect, designed for b - **[Browser Extension Build](/apps/demo-wallet/EXTENSION.md)** - How to build and load the demo wallet as a Chrome extension - **[JS Bridge Usage](/packages/walletkit/examples/js-bridge-usage.md)** - Implementing TonConnect JS Bridge for browser extension wallets +- **[KitGlobalOptions](/packages/walletkit/GLOBALOPTIONS.md)** - Configure global time provider for accurate transaction validation - **[iOS WalletKit](https://github.com/ton-connect/kit-ios)** - Swift Package providing TON wallet capabilities for iOS and macOS - **[Android WalletKit](https://github.com/ton-connect/kit-android)** - Kotlin/Java Package providing TON wallet capabilities for Android @@ -315,6 +316,25 @@ const info = kit.jettons.getJettonInfo(jettonAddress); // info?.name, info?.symbol, info?.image ``` +## Advanced Configuration + +### Custom Time Provider + +For production wallets, it's recommended to use server-synchronized time instead of device time to avoid issues with clock skew and timezone differences: + +```ts +import { KitGlobalOptions } from '@ton/walletkit'; + +// Set custom time provider before creating TonWalletKit +KitGlobalOptions.setGetCurrentTime(async () => { + const response = await fetch('https://your-api.com/time'); + const { timestamp } = await response.json(); + return timestamp; // Unix timestamp in seconds +}); +``` + +This ensures accurate `validUntil` validation for transactions. See [KitGlobalOptions documentation](/packages/walletkit/GLOBALOPTIONS.md) for detailed usage and best practices. + ## Sending assets programmatically You can create transactions from your wallet app (not from dApps) and feed them into the regular approval flow via `handleNewTransaction`. This triggers your `onTransactionRequest` callback, allowing the same UI confirmation flow for both dApp and wallet-initiated transactions. diff --git a/packages/walletkit/package.json b/packages/walletkit/package.json index 37a20c6ef..444582115 100644 --- a/packages/walletkit/package.json +++ b/packages/walletkit/package.json @@ -41,6 +41,7 @@ "lint": "eslint . --max-warnings 0", "lint:fix": "eslint . --max-warnings 0 --fix", "clean": "git clean -xdf dist node_modules .turbo", + "typecheck": "tsc --noEmit", "generate-openapi-spec": "src/api/scripts/generate-openapi-spec.sh" }, "keywords": [ diff --git a/packages/walletkit/src/contracts/v4r2/WalletV4R2.ts b/packages/walletkit/src/contracts/v4r2/WalletV4R2.ts index 8ffbfa630..3657280af 100644 --- a/packages/walletkit/src/contracts/v4r2/WalletV4R2.ts +++ b/packages/walletkit/src/contracts/v4r2/WalletV4R2.ts @@ -12,6 +12,7 @@ import type { Address, Cell, Contract, ContractProvider, Sender, MessageRelaxed import { beginCell, contractAddress, SendMode, storeMessageRelaxed } from '@ton/core'; import type { Maybe } from '@ton/core/dist/utils/maybe'; +import { KitGlobalOptions } from '../../core/KitGlobalOptions'; import type { ApiClient } from '../../types/toncenter/ApiClient'; import { ParseStack } from '../../utils/tvmStack'; import { asAddressFriendly } from '../../utils'; @@ -142,8 +143,13 @@ export class WalletV4R2 implements Contract { /** * Create transfer message body */ - createTransfer(args: { seqno: number; sendMode: SendMode; messages: MessageRelaxed[]; timeout?: number }): Cell { - const timeout = args.timeout ?? Math.floor(Date.now() / 1000) + 60; + async createTransfer(args: { + seqno: number; + sendMode: SendMode; + messages: MessageRelaxed[]; + timeout?: number; + }): Promise { + const timeout = args.timeout ?? (await KitGlobalOptions.getCurrentTime()) + 60; let body = beginCell() .storeUint(this.subwalletId, 32) @@ -173,7 +179,7 @@ export class WalletV4R2 implements Contract { timeout?: number; }, ): Promise { - const transfer = this.createTransfer(args); + const transfer = await this.createTransfer(args); await provider.internal(via, { sendMode: SendMode.PAY_GAS_SEPARATELY, body: transfer, diff --git a/packages/walletkit/src/contracts/v4r2/WalletV4R2Adapter.ts b/packages/walletkit/src/contracts/v4r2/WalletV4R2Adapter.ts index 03ce31853..6ba8d8ca0 100644 --- a/packages/walletkit/src/contracts/v4r2/WalletV4R2Adapter.ts +++ b/packages/walletkit/src/contracts/v4r2/WalletV4R2Adapter.ts @@ -153,10 +153,6 @@ export class WalletV4R2Adapter implements WalletAdapter { // } - const timeout = input.validUntil - ? Math.min(input.validUntil, Math.floor(Date.now() / 1000) + 600) - : Math.floor(Date.now() / 1000) + 60; - try { const messages: MessageRelaxed[] = input.messages.map((m) => { let bounce = true; @@ -176,11 +172,11 @@ export class WalletV4R2Adapter implements WalletAdapter { init: m.stateInit ? loadStateInit(Cell.fromBase64(m.stateInit).asSlice()) : undefined, }); }); - const data = this.walletContract.createTransfer({ + const data = await this.walletContract.createTransfer({ seqno: seqno, sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, messages, - timeout: timeout, + timeout: input.validUntil, }); const signature = await this.sign(Uint8Array.from(data.hash())); diff --git a/packages/walletkit/src/contracts/w5/WalletV5R1Adapter.ts b/packages/walletkit/src/contracts/w5/WalletV5R1Adapter.ts index f37ba3610..54bab31f7 100644 --- a/packages/walletkit/src/contracts/w5/WalletV5R1Adapter.ts +++ b/packages/walletkit/src/contracts/w5/WalletV5R1Adapter.ts @@ -35,6 +35,7 @@ import type { Hex, Base64String, } from '../../api/models'; +import { KitGlobalOptions } from '../../core/KitGlobalOptions'; import type { Feature } from '../../types/jsBridge'; const log = globalLogger.createChild('WalletV5R1Adapter'); @@ -205,9 +206,10 @@ export class WalletV5R1Adapter implements WalletAdapter { ...options, validUntil: undefined, }; - // add valid untill + + // add validUntil if (input.validUntil) { - const now = Math.floor(Date.now() / 1000); + const now = await KitGlobalOptions.getCurrentTime(); const maxValidUntil = now + 600; if (input.validUntil < now) { throw new WalletKitError( @@ -315,7 +317,7 @@ export class WalletV5R1Adapter implements WalletAdapter { auth_signed: 0x7369676e, }; - const expireAt = options.validUntil ?? Math.floor(Date.now() / 1000) + 300; + const expireAt = options.validUntil ?? (await KitGlobalOptions.getCurrentTime()) + 300; const payload = beginCell() .storeUint(Opcodes.auth_signed, 32) .storeUint(walletId, 32) diff --git a/packages/walletkit/src/core/BridgeManager.ts b/packages/walletkit/src/core/BridgeManager.ts index b0b0afff7..fce0c6cc0 100644 --- a/packages/walletkit/src/core/BridgeManager.ts +++ b/packages/walletkit/src/core/BridgeManager.ts @@ -31,6 +31,7 @@ import type { Analytics, AnalyticsManager } from '../analytics'; import type { TonWalletKitOptions } from '../types/config'; import { TONCONNECT_BRIDGE_RESPONSE } from '../bridge/JSBridgeInjector'; import type { BridgeEvent } from '../api/models'; +import { KitGlobalOptions } from '../core/KitGlobalOptions'; const log = globalLogger.createChild('BridgeManager'); @@ -578,7 +579,7 @@ export class BridgeManager { method: event.method || 'unknown', params: event.params || event, // sessionId: event.from, - timestamp: Date.now(), + timestamp: await KitGlobalOptions.getCurrentTime(), from: event?.from, domain: event?.domain, isJsBridge: event?.isJsBridge, diff --git a/packages/walletkit/src/core/KitGlobalOptions.ts b/packages/walletkit/src/core/KitGlobalOptions.ts new file mode 100644 index 000000000..b198161d4 --- /dev/null +++ b/packages/walletkit/src/core/KitGlobalOptions.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { getUnixtime } from '../utils/time'; + +type GetCurrentTimeFunc = () => Promise | number; + +export class KitGlobalOptions { + private static getCurrentTimeImpl: GetCurrentTimeFunc = getUnixtime; + + static setGetCurrentTime(fn: GetCurrentTimeFunc): void { + KitGlobalOptions.getCurrentTimeImpl = fn; + } + + static async getCurrentTime(): Promise { + return KitGlobalOptions.getCurrentTimeImpl(); + } +} diff --git a/packages/walletkit/src/core/RequestProcessor.ts b/packages/walletkit/src/core/RequestProcessor.ts index 8191d4400..4912b5f2b 100644 --- a/packages/walletkit/src/core/RequestProcessor.ts +++ b/packages/walletkit/src/core/RequestProcessor.ts @@ -27,7 +27,9 @@ import { } from '@tonconnect/protocol'; import { getSecureRandomBytes } from '@ton/crypto'; +import { KitGlobalOptions } from '../core/KitGlobalOptions'; import type { EventSignDataApproval, TonWalletKitOptions } from '../types'; +import { isBefore } from '../utils/time'; import type { SessionManager } from './SessionManager'; import type { BridgeManager } from './BridgeManager'; import { globalLogger } from './Logger'; @@ -777,6 +779,7 @@ export class RequestProcessor { { eventId: event.id }, ); } + const wallet = this.getWalletFromEvent(event); if (!wallet) { throw new WalletKitError( @@ -788,16 +791,13 @@ export class RequestProcessor { } const validUntil = event.request.validUntil; - if (validUntil) { - const now = Math.floor(Date.now() / 1000); - if (validUntil < now) { - throw new WalletKitError( - ERROR_CODES.VALIDATION_ERROR, - 'Transaction valid_until timestamp is in the past', - undefined, - { validUntil, currentTime: now }, - ); - } + if (validUntil && (await isBefore(validUntil))) { + throw new WalletKitError( + ERROR_CODES.VALIDATION_ERROR, + 'Transaction expired: valid_until timestamp is in the past', + undefined, + { validUntil, currentTime: await KitGlobalOptions.getCurrentTime() }, + ); } return await signTransactionInternal(wallet, event.request); diff --git a/packages/walletkit/src/core/TonWalletKit.spec.ts b/packages/walletkit/src/core/TonWalletKit.spec.ts index 769d3e38f..828e1443d 100644 --- a/packages/walletkit/src/core/TonWalletKit.spec.ts +++ b/packages/walletkit/src/core/TonWalletKit.spec.ts @@ -7,14 +7,17 @@ */ import { CHAIN } from '@tonconnect/protocol'; -import { Address } from '@ton/core'; +import { Address, toNano } from '@ton/core'; +import { vi } from 'vitest'; import { mockFn, mocked, useFakeTimers, useRealTimers } from '../../mock.config'; import { TonWalletKit } from './TonWalletKit'; +import { KitGlobalOptions } from './KitGlobalOptions'; import type { TonWalletKitOptions } from '../types'; import { createDummyWallet, createMockApiClient } from '../contracts/w5/WalletV5R1.fixture'; import type { InjectedToExtensionBridgeRequest, InjectedToExtensionBridgeRequestPayload } from '../types/jsBridge'; -import type { TONTransferRequest } from '../api/models'; +import type { TONTransferRequest, TransactionRequest } from '../api/models'; +import { getUnixtime } from '../utils'; const mockApiClient = createMockApiClient(); @@ -31,6 +34,7 @@ describe('TonWalletKit', () => { afterEach(() => { useRealTimers(); + KitGlobalOptions.setGetCurrentTime(getUnixtime); }); const createKit = async () => { @@ -132,4 +136,179 @@ describe('TonWalletKit', () => { await kit.close(); }); + + describe('validUntil validation', () => { + it('should accept transaction with future validUntil', async () => { + const kit = await createKit(); + const wallet = await kit.addWallet(await createDummyWallet(1n)); + + expect(wallet).toBeDefined(); + + if (!wallet) { + throw new Error('Wallet not created'); + } + + // Set current time to 1000ms (1 second) + vi.setSystemTime(1000); + + // Sync KitGlobalOptions with fake timer + KitGlobalOptions.setGetCurrentTime(() => Math.floor(Date.now() / 1000)); + + kit.onTransactionRequest(() => { + // + }); + + const request: TransactionRequest = { + messages: [ + { + address: wallet.getAddress(), + amount: toNano('1').toString(), + }, + ], + validUntil: Math.floor((Date.now() + 10000) / 1000), // 10 seconds in future (11 seconds total) + }; + + // Should not throw + await expect(kit.handleNewTransaction(wallet, request)).resolves.not.toThrow(); + + await kit.close(); + }); + + it('should reject transaction with past validUntil', async () => { + const kit = await createKit(); + const wallet = await kit.addWallet(await createDummyWallet(1n)); + + expect(wallet).toBeDefined(); + + if (!wallet) { + throw new Error('Wallet not created'); + } + + // Set current time to 10000ms (10 seconds) + vi.setSystemTime(10000); + + // Sync KitGlobalOptions with fake timer + KitGlobalOptions.setGetCurrentTime(() => Math.floor(Date.now() / 1000)); + + let errorReceived = false; + let errorMessage = ''; + + const errorPromise = new Promise((resolve) => { + kit.onRequestError((event) => { + errorReceived = true; + errorMessage = event.error.message || ''; + resolve(); + }); + }); + + kit.onTransactionRequest(() => { + // Should not be called for invalid transactions + throw new Error('onTransactionRequest should not be called for invalid transactions'); + }); + + const request: TransactionRequest = { + messages: [ + { + address: wallet.getAddress(), + amount: toNano('1').toString(), + }, + ], + validUntil: Math.floor((Date.now() - 5000) / 1000), // 5 seconds in past (5 seconds) + }; + + // handleNewTransaction should not throw, but error callback should be called + await kit.handleNewTransaction(wallet, request); + + // Wait for error callback with timeout + await Promise.race([ + errorPromise, + new Promise((_, reject) => setTimeout(() => reject(new Error('Error callback not called')), 1000)), + ]); + + expect(errorReceived).toBe(true); + expect(errorMessage).toBe('Failed to parse transaction request'); + + await kit.close(); + }); + + it('should accept transaction without validUntil', async () => { + const kit = await createKit(); + const wallet = await kit.addWallet(await createDummyWallet(1n)); + + expect(wallet).toBeDefined(); + + if (!wallet) { + throw new Error('Wallet not created'); + } + + kit.onTransactionRequest((event) => { + expect(event.walletId).toBe(wallet.getWalletId()); + }); + + const request: TransactionRequest = { + messages: [ + { + address: wallet.getAddress(), + amount: toNano('1').toString(), + }, + ], + // No validUntil + }; + + // Should not throw without validUntil + await expect(kit.handleNewTransaction(wallet, request)).resolves.not.toThrow(); + + await kit.close(); + }); + + it('should use custom getCurrentTime function when provided', async () => { + // Set fake time to 5000ms (5 seconds) + vi.setSystemTime(5000); + + const customTime = Math.floor(Date.now() / 1000) + 1000; // 1000 seconds in future from fake time (1005 seconds) + + // Set custom time provider + KitGlobalOptions.setGetCurrentTime(() => { + return customTime; + }); + + const kit = new TonWalletKit({ + networks: { [CHAIN.MAINNET]: {} }, + storage: { + get: mockFn().mockResolvedValue(null), + set: mockFn().mockResolvedValue(undefined), + remove: mockFn().mockResolvedValue(undefined), + clear: mockFn().mockResolvedValue(undefined), + }, + }); + await kit.waitForReady(); + + const wallet = await kit.addWallet(await createDummyWallet(1n)); + + expect(wallet).toBeDefined(); + + if (!wallet) { + throw new Error('Wallet not created'); + } + + kit.onTransactionRequest((event) => { + expect(event.walletId).toBe(wallet.getWalletId()); + }); + + const request: TransactionRequest = { + messages: [ + { + address: wallet.getAddress(), + amount: toNano('1').toString(), + }, + ], + validUntil: customTime + 500, // 500 seconds after custom time (1505 seconds) + }; + + // Should not throw because validUntil is in the future relative to custom time + await expect(kit.handleNewTransaction(wallet, request)).resolves.not.toThrow(); + + await kit.close(); + }); + }); }); diff --git a/packages/walletkit/src/core/TonWalletKit.ts b/packages/walletkit/src/core/TonWalletKit.ts index 79e86bb46..cbd3c4140 100644 --- a/packages/walletkit/src/core/TonWalletKit.ts +++ b/packages/walletkit/src/core/TonWalletKit.ts @@ -61,6 +61,7 @@ import type { SignDataApprovalResponse, } from '../api/models'; import { asAddressFriendly } from '../utils'; +import { KitGlobalOptions } from './KitGlobalOptions'; const log = globalLogger.createChild('TonWalletKit'); @@ -528,7 +529,7 @@ export class TonWalletKit implements ITonWalletKit { async handleNewTransaction(wallet: Wallet, data: TransactionRequest): Promise { await this.ensureInitialized(); - data.validUntil ??= Math.floor(Date.now() / 1000) + 300; + data.validUntil ??= (await KitGlobalOptions.getCurrentTime()) + 300; data.network ??= wallet.getNetwork(); const walletId = wallet.getWalletId(); diff --git a/packages/walletkit/src/core/wallet/extensions/ton.ts b/packages/walletkit/src/core/wallet/extensions/ton.ts index 110acdf33..c7dc12097 100644 --- a/packages/walletkit/src/core/wallet/extensions/ton.ts +++ b/packages/walletkit/src/core/wallet/extensions/ton.ts @@ -23,6 +23,8 @@ import type { Base64String, } from '../../../api/models'; import type { Wallet, WalletTonInterface } from '../../../api/interfaces'; +import { KitGlobalOptions } from '../../KitGlobalOptions'; +import { isBefore } from '../../../utils/time'; const log = globalLogger.createChild('WalletTonClass'); @@ -117,6 +119,15 @@ export class WalletTonClass implements WalletTonInterface { async sendTransaction(this: Wallet, request: TransactionRequest): Promise { try { + if (request.validUntil !== undefined && (await isBefore(request.validUntil))) { + throw new WalletKitError( + ERROR_CODES.INVALID_REQUEST_EVENT, + 'Transaction validUntil has expired', + undefined, + { validUntil: request.validUntil, currentTime: await KitGlobalOptions.getCurrentTime() }, + ); + } + const boc = await this.getSignedSendTransaction(request); await CallForSuccess(() => this.getClient().sendBoc(boc)); diff --git a/packages/walletkit/src/handlers/TransactionHandler.ts b/packages/walletkit/src/handlers/TransactionHandler.ts index 5f933f57a..3d429a06b 100644 --- a/packages/walletkit/src/handlers/TransactionHandler.ts +++ b/packages/walletkit/src/handlers/TransactionHandler.ts @@ -11,6 +11,7 @@ import type { SendTransactionRpcResponseError, WalletResponseTemplateError } fro import { CHAIN, SEND_TRANSACTION_ERROR_CODES } from '@tonconnect/protocol'; import type { ValidationResult } from '../types'; +import { isBefore } from '../utils/time'; import { toTransactionRequest } from '../types/internal'; import type { RawBridgeEvent, @@ -88,7 +89,7 @@ export class TransactionHandler } as SendTransactionRpcResponseError; } - const requestValidation = this.parseTonConnectTransactionRequest(event, wallet); + const requestValidation = await this.parseTonConnectTransactionRequest(event, wallet); if (!requestValidation.result || !requestValidation?.validation?.isValid) { log.error('Failed to parse transaction request', { event, requestValidation }); this.eventEmitter.emit('event:error', event); @@ -158,14 +159,13 @@ export class TransactionHandler /** * Parse raw transaction request from bridge event */ - - private parseTonConnectTransactionRequest( + private async parseTonConnectTransactionRequest( event: RawBridgeEventTransaction, wallet: Wallet, - ): { + ): Promise<{ result: TransactionRequest | undefined; validation: ValidationResult; - } { + }> { let errors: string[] = []; try { if (event.params.length !== 1) { @@ -178,7 +178,7 @@ export class TransactionHandler } const params = JSON.parse(event.params[0]) as ConnectTransactionParamContent; - const validUntilValidation = this.validateValidUntil(params.valid_until); + const validUntilValidation = await this.validateValidUntil(params.valid_until); if (!validUntilValidation.isValid) { errors = errors.concat(validUntilValidation.errors); } else { @@ -222,8 +222,7 @@ export class TransactionHandler /** * Parse network from various possible formats */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private validateNetwork(network: any, wallet: Wallet): ReturnWithValidationResult { + private validateNetwork(network: unknown, wallet: Wallet): ReturnWithValidationResult { let errors: string[] = []; if (typeof network === 'string') { if (network === '-3' || network === '-239') { @@ -270,19 +269,18 @@ export class TransactionHandler /** * Parse validUntil timestamp */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private validateValidUntil(validUntil: any): ReturnWithValidationResult { + private async validateValidUntil(validUntil: unknown): Promise> { let errors: string[] = []; if (typeof validUntil === 'undefined') { return { result: 0, isValid: errors.length === 0, errors: errors }; } + if (typeof validUntil !== 'number' || isNaN(validUntil)) { errors.push('Invalid validUntil timestamp not a number'); return { result: 0, isValid: errors.length === 0, errors: errors }; } - const now = Math.floor(Date.now() / 1000); - if (validUntil < now) { + if (await isBefore(validUntil)) { errors.push('Invalid validUntil timestamp'); return { result: 0, isValid: errors.length === 0, errors: errors }; } diff --git a/packages/walletkit/src/index.ts b/packages/walletkit/src/index.ts index 7513e8772..9b367b78f 100644 --- a/packages/walletkit/src/index.ts +++ b/packages/walletkit/src/index.ts @@ -12,6 +12,7 @@ export { TonWalletKit } from './core/TonWalletKit'; export * from './types'; export type * from './types/internal'; export * from './errors'; +export { KitGlobalOptions } from './core/KitGlobalOptions'; export { WalletManager } from './core/WalletManager'; export { SessionManager } from './core/SessionManager'; export { BridgeManager } from './core/BridgeManager'; diff --git a/packages/walletkit/src/types/config.ts b/packages/walletkit/src/types/config.ts index f665deff6..19b937062 100644 --- a/packages/walletkit/src/types/config.ts +++ b/packages/walletkit/src/types/config.ts @@ -55,11 +55,6 @@ export interface TonWalletKitOptions { bridge?: BridgeConfig; /** Storage settings */ storage?: StorageConfig | StorageAdapter; - /** Validation settings */ - validation?: { - strictMode?: boolean; - allowUnknownWalletVersions?: boolean; - }; /** Event processor settings */ eventProcessor?: EventProcessorConfig; diff --git a/packages/walletkit/src/types/internal.ts b/packages/walletkit/src/types/internal.ts index a9888caf9..42fc13996 100644 --- a/packages/walletkit/src/types/internal.ts +++ b/packages/walletkit/src/types/internal.ts @@ -14,7 +14,6 @@ import type { SignDataRpcRequest, WalletResponseTemplateError, } from '@tonconnect/protocol'; -import { WalletResponseError as _WalletResponseError } from '@tonconnect/protocol'; import type { JSBridgeTransportFunction } from './jsBridge'; import type { WalletId } from '../utils/walletId'; @@ -29,8 +28,6 @@ import type { import { SendModeFromValue, SendModeToValue } from '../api/models'; import { asAddressFriendly } from '../utils/address'; -// import type { WalletInterface } from './wallet'; - export interface SessionData { sessionId: string; diff --git a/packages/walletkit/src/utils/time.ts b/packages/walletkit/src/utils/time.ts index a095ecd9f..be0d1259e 100644 --- a/packages/walletkit/src/utils/time.ts +++ b/packages/walletkit/src/utils/time.ts @@ -6,6 +6,14 @@ * */ +import { KitGlobalOptions } from '../core/KitGlobalOptions'; + export function getUnixtime(): number { return Math.floor(Date.now() / 1000); } + +export async function isBefore(timestamp1: number, timestamp2?: number): Promise { + const calculated = timestamp2 ?? (await KitGlobalOptions.getCurrentTime()); + + return timestamp1 < calculated; +} diff --git a/packages/walletkit/src/utils/toncenterEmulation.ts b/packages/walletkit/src/utils/toncenterEmulation.ts index 4008f76a5..83221a832 100644 --- a/packages/walletkit/src/utils/toncenterEmulation.ts +++ b/packages/walletkit/src/utils/toncenterEmulation.ts @@ -23,6 +23,7 @@ import type { import { Result, SendModeToValue, AssetType } from '../api/models'; import type { Wallet } from '../api/interfaces'; import { asAddressFriendly, asMaybeAddressFriendly } from './address'; +import { KitGlobalOptions } from '../core/KitGlobalOptions'; import type { ApiClient } from '../types/toncenter/ApiClient'; // import { ConnectMessageTransactionMessage } from '@/types/connect'; @@ -53,10 +54,10 @@ const TON_PROXY_ADDRESSES = [ /** * Creates a toncenter message payload for emulation */ -export function createToncenterMessage( +export async function createToncenterMessage( walletAddress: string | undefined, messages: TransactionRequest['messages'], -): ToncenterMessage { +): Promise { return { method: 'POST', headers: { @@ -64,7 +65,7 @@ export function createToncenterMessage( }, body: JSON.stringify({ from: walletAddress, - valid_until: Math.floor(Date.now() / 1000) + 60, + valid_until: (await KitGlobalOptions.getCurrentTime()) + 60, include_code_data: true, include_address_book: true, include_metadata: true, diff --git a/packages/walletkit/src/validation/events.ts b/packages/walletkit/src/validation/events.ts index 74d631e62..93799792e 100644 --- a/packages/walletkit/src/validation/events.ts +++ b/packages/walletkit/src/validation/events.ts @@ -9,6 +9,7 @@ // Bridge event validation logic import type { ValidationResult, ValidationContext } from './types'; +import { KitGlobalOptions } from '../core/KitGlobalOptions'; /** * Validate bridge event structure @@ -88,7 +89,7 @@ export function validateConnectEventParams(params: any): ValidationResult { * Validate transaction event parameters */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function validateTransactionEventParams(params: any): ValidationResult { +export async function validateTransactionEventParams(params: any): Promise { const errors: string[] = []; if (!params || typeof params !== 'object') { @@ -109,7 +110,7 @@ export function validateTransactionEventParams(params: any): ValidationResult { if (params.validUntil && typeof params.validUntil !== 'number') { errors.push('validUntil must be a number if provided'); - } else if (params.validUntil && params.validUntil <= Date.now() / 1000) { + } else if (params.validUntil && params.validUntil <= (await KitGlobalOptions.getCurrentTime())) { errors.push('validUntil must be a future timestamp'); } diff --git a/packages/walletkit/src/validation/transaction.ts b/packages/walletkit/src/validation/transaction.ts index 6509020e7..bbef5007f 100644 --- a/packages/walletkit/src/validation/transaction.ts +++ b/packages/walletkit/src/validation/transaction.ts @@ -8,6 +8,7 @@ import { Cell } from '@ton/core'; +import { KitGlobalOptions } from '../core/KitGlobalOptions'; import type { ValidationResult } from './types'; import { validateTonAddress } from './address'; import { isFriendlyTonAddress } from '../utils/address'; @@ -147,8 +148,11 @@ export function validateMessageObject(message: any): ValidationResult { /** * Validate transaction request structure */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function validateTransactionRequest(request: any, isTonConnect: boolean = true): ValidationResult { +export async function validateTransactionRequest( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + request: any, + isTonConnect: boolean = true, +): Promise { const errors: string[] = []; if (!request || typeof request !== 'object') { @@ -173,7 +177,7 @@ export function validateTransactionRequest(request: any, isTonConnect: boolean = if (request.validUntil) { if (typeof request.validUntil !== 'number') { errors.push('validUntil must be a number'); - } else if (request.validUntil <= Math.floor(Date.now() / 1000)) { + } else if (request.validUntil <= (await KitGlobalOptions.getCurrentTime())) { errors.push('validUntil must be a future timestamp'); } }