Skip to content

Commit 3e3a217

Browse files
authored
Merge pull request #534 from algorandfoundation/feat/time-management
Feature - time management
2 parents 556d6be + bb8b118 commit 3e3a217

File tree

4 files changed

+480
-41
lines changed

4 files changed

+480
-41
lines changed

src/algorand-client.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { SuggestedParams } from '@algorandfoundation/algokit-algod-client'
22
import { Address, ReadableAddress } from '@algorandfoundation/algokit-common'
3-
import { AddressWithTransactionSigner, LogicSigAccount, TransactionSigner, MultisigAccount } from '@algorandfoundation/algokit-transact'
3+
import { nobleEd25519Generator } from '@algorandfoundation/algokit-crypto'
4+
import { AddressWithTransactionSigner, LogicSigAccount, MultisigAccount, TransactionSigner } from '@algorandfoundation/algokit-transact'
45
import { AccountManager, AccountManagerConfig } from './account-manager'
56
import { AlgorandClientTransactionCreator } from './algorand-client-transaction-creator'
67
import { AlgorandClientTransactionSender } from './algorand-client-transaction-sender'
@@ -10,8 +11,7 @@ import { AssetManager } from './asset-manager'
1011
import { AlgoSdkClients, ClientManager } from './client-manager'
1112
import { ErrorTransformer, TransactionComposer, TransactionComposerConfig } from './composer'
1213
import { AlgoConfig } from './network-client'
13-
import { nobleEd25519Generator } from '@algorandfoundation/algokit-crypto'
14-
14+
import { NetworkManager } from './network-manager'
1515

1616
/**
1717
* A client that brokers easy access to Algorand functionality.
@@ -22,6 +22,7 @@ export class AlgorandClient {
2222
private _appManager: AppManager
2323
private _appDeployer: AppDeployer
2424
private _assetManager: AssetManager
25+
private _networkManager: NetworkManager
2526
private _transactionSender: AlgorandClientTransactionSender
2627
private _transactionCreator: AlgorandClientTransactionCreator
2728

@@ -43,6 +44,7 @@ export class AlgorandClient {
4344
this._accountManager = new AccountManager(this._clientManager, { ed25519Generator: config.ed25519Generator ?? nobleEd25519Generator })
4445
this._appManager = new AppManager(this._clientManager.algod)
4546
this._assetManager = new AssetManager(this._clientManager.algod, (config) => this.newGroup(config))
47+
this._networkManager = new NetworkManager(this._clientManager.algod, this)
4648
this._transactionSender = new AlgorandClientTransactionSender((config) => this.newGroup(config), this._assetManager, this._appManager)
4749
this._transactionCreator = new AlgorandClientTransactionCreator((config) => this.newGroup(config))
4850
this._appDeployer = new AppDeployer(this._appManager, this._transactionSender, this._clientManager.indexerIfPresent)
@@ -214,6 +216,26 @@ export class AlgorandClient {
214216
return this._appDeployer
215217
}
216218

219+
/**
220+
* Methods for interacting with the network.
221+
* Provides utilities for querying blockchain state and waiting for specific conditions.
222+
* @returns The `NetworkManager` instance.
223+
* @example
224+
* ```typescript
225+
* // Get last round
226+
* const lastRound = await algorand.network.getLastRound()
227+
*
228+
* // Wait for a specific round
229+
* await algorand.network.waitUntilRound(1000n)
230+
*
231+
* // LocalNet-specific: block warp
232+
* await algorand.network.localNet.blockWarp(100n)
233+
* ```
234+
*/
235+
public get network() {
236+
return this._networkManager
237+
}
238+
217239
/**
218240
* Register a function that will be used to transform an error caught when simulating or executing
219241
* composed transaction groups made from `newGroup`

src/index.ts

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,89 +2,91 @@
22
* @module algokit-utils
33
*/
44
export {
5-
ALGORAND_ZERO_ADDRESS_STRING,
65
Address,
6+
ALGORAND_ZERO_ADDRESS_STRING,
77
decodeAddress,
88
encodeAddress,
99
getAddress,
1010
getApplicationAddress,
1111
getOptionalAddress,
1212
} from '../packages/common/src/address'
1313
export type { Addressable, ReadableAddress } from '../packages/common/src/address'
14+
export { AlgorandClient } from './algorand-client'
1415
export * from './amount'
1516
export * from './config'
16-
export * from './transaction'
17-
export { AlgorandClient } from './algorand-client'
1817
export * from './debugging'
1918
export * from './lifecycle-events'
2019

2120
// Manager classes and related types
21+
export type { AccountAssetInformation, AccountInformation } from './account'
2222
export { AccountManager, getAccountTransactionSigner } from './account-manager'
2323
export type { EnsureFundedResult } from './account-manager'
2424
export { AlgorandClientTransactionCreator } from './algorand-client-transaction-creator'
2525
export { AlgorandClientTransactionSender } from './algorand-client-transaction-sender'
26+
export type {
27+
AppDeployMetadata,
28+
AppState,
29+
BoxName,
30+
CompiledTeal,
31+
OnSchemaBreak,
32+
OnUpdate,
33+
SendAppCreateTransactionResult,
34+
SendAppUpdateTransactionResult,
35+
TealTemplateParams,
36+
} from './app'
37+
export { AppClient } from './app-client'
38+
export type { AppClientParams, ResolveAppClientByCreatorAndName } from './app-client'
2639
export { AppDeployer } from './app-deployer'
2740
export type {
28-
DeployAppUpdateParams,
29-
DeployAppUpdateMethodCall,
30-
DeployAppDeleteParams,
31-
DeployAppDeleteMethodCall,
3241
AppDeployParams,
33-
AppMetadata,
34-
AppLookup,
3542
AppDeployResult,
43+
AppLookup,
44+
AppMetadata,
45+
DeployAppDeleteMethodCall,
46+
DeployAppDeleteParams,
47+
DeployAppUpdateMethodCall,
48+
DeployAppUpdateParams,
3649
} from './app-deployer'
50+
export { AppFactory } from './app-factory'
51+
export type { AppFactoryParams } from './app-factory'
3752
export { AppManager } from './app-manager'
3853
export type { AppInformation, BoxIdentifier, BoxReference, BoxValueRequestParams, BoxValuesRequestParams } from './app-manager'
3954
export { AssetManager } from './asset-manager'
40-
export type { BulkAssetOptInOutResult, AssetInformation } from './asset-manager'
55+
export type { AssetInformation, BulkAssetOptInOutResult } from './asset-manager'
56+
export { AsyncEventEmitter } from './async-event-emitter'
4157
export { ClientManager } from './client-manager'
4258
export type {
4359
AlgoSdkClients,
60+
ClientAppClientByNetworkParams,
61+
ClientAppClientParams,
4462
ClientAppFactoryParams,
4563
ClientResolveAppClientByCreatorAndNameParams,
46-
ClientAppClientParams,
47-
ClientAppClientByNetworkParams,
4864
ClientTypedAppClientByCreatorAndNameParams,
49-
ClientTypedAppClientParams,
5065
ClientTypedAppClientByNetworkParams,
66+
ClientTypedAppClientParams,
5167
ClientTypedAppFactoryParams,
5268
TypedAppClient,
5369
TypedAppFactory,
5470
} from './client-manager'
5571
export { TransactionComposer } from './composer'
5672
export type {
73+
BuiltTransactions,
74+
ErrorTransformer,
5775
RawSimulateOptions,
58-
SkipSignaturesSimulateOptions,
5976
SimulateOptions,
60-
ErrorTransformer,
77+
SkipSignaturesSimulateOptions,
6178
TransactionComposerConfig,
6279
TransactionComposerParams,
63-
BuiltTransactions,
6480
} from './composer'
81+
export { TestNetDispenserApiClient } from './dispenser-client'
6582
export type { LookupAssetHoldingsOptions } from './indexer'
66-
export type { AlgoClientConfig, AlgoConfig, NetworkDetails } from './network-client'
83+
export { KmdAccountManager } from './kmd-account-manager'
6784
export { genesisIdIsLocalNet } from './network-client'
85+
export type { AlgoClientConfig, AlgoConfig, NetworkDetails } from './network-client'
86+
export { LocalNetManager, NetworkManager } from './network-manager'
87+
export type { WaitUntilTimestampOptions } from './network-manager'
88+
export * from './transaction'
89+
export type { AppCreateParams, AppDeleteParams, AppUpdateParams } from './transactions/app-call'
90+
export type { AppCreateMethodCall, AppDeleteMethodCall, AppUpdateMethodCall } from './transactions/method-call'
6891
export { UpdatableConfig } from './updatable-config'
6992
export type { Config as AlgoKitConfig } from './updatable-config'
70-
export type {
71-
CompiledTeal,
72-
SendAppCreateTransactionResult,
73-
SendAppUpdateTransactionResult,
74-
AppState,
75-
AppDeployMetadata,
76-
BoxName,
77-
TealTemplateParams,
78-
OnSchemaBreak,
79-
OnUpdate,
80-
} from './app'
81-
export type { AccountInformation, AccountAssetInformation } from './account'
82-
export type { AppClientParams, ResolveAppClientByCreatorAndName } from './app-client'
83-
export { AppClient } from './app-client'
84-
export type { AppFactoryParams } from './app-factory'
85-
export { AppFactory } from './app-factory'
86-
export type { AppDeleteMethodCall, AppUpdateMethodCall, AppCreateMethodCall } from './transactions/method-call'
87-
export type { AppDeleteParams, AppUpdateParams, AppCreateParams } from './transactions/app-call'
88-
export { AsyncEventEmitter } from './async-event-emitter'
89-
export { KmdAccountManager } from './kmd-account-manager'
90-
export { TestNetDispenserApiClient } from './dispenser-client'

src/network-manager.spec.ts

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { beforeAll, describe, expect, it } from 'vitest'
2+
import { AlgorandClient } from './algorand-client'
3+
import { algorandFixture } from './testing'
4+
5+
describe('NetworkManager', () => {
6+
describe('LocalNetManager', () => {
7+
const fixture = algorandFixture()
8+
let algorand: AlgorandClient
9+
10+
beforeAll(async () => {
11+
await fixture.newScope()
12+
algorand = fixture.algorand
13+
})
14+
15+
describe('blockWarp', () => {
16+
it('should advance to target round', async () => {
17+
const currentRound = await algorand.network.getLastRound()
18+
const targetRound = currentRound + 10n
19+
20+
await algorand.network.localNet.blockWarp(targetRound)
21+
22+
const newRound = await algorand.network.getLastRound()
23+
expect(newRound).toBeGreaterThanOrEqual(targetRound)
24+
})
25+
26+
it('should do nothing if target round is already reached', async () => {
27+
const currentRound = await algorand.network.getLastRound()
28+
const targetRound = currentRound - 5n
29+
30+
await algorand.network.localNet.blockWarp(targetRound)
31+
32+
const newRound = await algorand.network.getLastRound()
33+
expect(newRound).toBeGreaterThanOrEqual(currentRound)
34+
})
35+
})
36+
37+
describe('timeWarp', () => {
38+
it('should advance to target timestamp', async () => {
39+
const currentTimestamp = await algorand.network.getLatestTimestamp()
40+
const targetTimestamp = currentTimestamp + 3600n // 1 hour in the future
41+
42+
await algorand.network.localNet.timeWarp(targetTimestamp)
43+
44+
const newTimestamp = await algorand.network.getLatestTimestamp()
45+
expect(newTimestamp).toBeGreaterThanOrEqual(targetTimestamp)
46+
})
47+
48+
it('should handle small time jumps', async () => {
49+
const currentTimestamp = await algorand.network.getLatestTimestamp()
50+
const targetTimestamp = currentTimestamp + 60n // 1 minute in the future
51+
52+
await algorand.network.localNet.timeWarp(targetTimestamp)
53+
54+
const newTimestamp = await algorand.network.getLatestTimestamp()
55+
expect(newTimestamp).toBeGreaterThanOrEqual(targetTimestamp)
56+
})
57+
58+
it('should handle large time jumps', async () => {
59+
const currentTimestamp = await algorand.network.getLatestTimestamp()
60+
const targetTimestamp = currentTimestamp + 86400n // 1 day in the future
61+
62+
await algorand.network.localNet.timeWarp(targetTimestamp)
63+
64+
const newTimestamp = await algorand.network.getLatestTimestamp()
65+
expect(newTimestamp).toBeGreaterThanOrEqual(targetTimestamp)
66+
})
67+
})
68+
})
69+
70+
describe.skip('waitUntilRound (testnet - manual)', () => {
71+
const algorand = AlgorandClient.testNet()
72+
73+
it('should return immediately when target round is already reached', async () => {
74+
const currentRound = await algorand.network.getLastRound()
75+
const targetRound = currentRound - 10n
76+
77+
const startTime = Date.now()
78+
await algorand.network.waitUntilRound(targetRound)
79+
const elapsed = Date.now() - startTime
80+
81+
// Should return almost immediately (under 1 second)
82+
expect(elapsed).toBeLessThan(1000)
83+
})
84+
85+
it('should wait for target round within 1 minute', async () => {
86+
const currentRound = await algorand.network.getLastRound()
87+
// Wait for ~10 rounds ahead (~30 seconds on testnet with ~3s block time)
88+
const targetRound = currentRound + 10n
89+
90+
const startTime = Date.now()
91+
await algorand.network.waitUntilRound(targetRound)
92+
const elapsed = Date.now() - startTime
93+
94+
const finalRound = await algorand.network.getLastRound()
95+
expect(finalRound).toBeGreaterThanOrEqual(targetRound)
96+
97+
// Should complete within 1 minute
98+
expect(elapsed).toBeLessThan(60_000)
99+
}, 60_000)
100+
101+
it('should wait for target round that takes more than 1 minute', async () => {
102+
const currentRound = await algorand.network.getLastRound()
103+
// Wait for ~25 rounds ahead (~75 seconds on testnet with ~3s block time)
104+
const targetRound = currentRound + 25n
105+
106+
const startTime = Date.now()
107+
await algorand.network.waitUntilRound(targetRound)
108+
const elapsed = Date.now() - startTime
109+
110+
const finalRound = await algorand.network.getLastRound()
111+
expect(finalRound).toBeGreaterThanOrEqual(targetRound)
112+
113+
// Should take more than 1 minute but complete successfully
114+
expect(elapsed).toBeGreaterThan(60_000)
115+
}, 120_000)
116+
})
117+
118+
describe.skip('waitUntilTimestamp (testnet - manual)', () => {
119+
const algorand = AlgorandClient.testNet()
120+
121+
it('should return immediately when target timestamp is already reached', async () => {
122+
const currentTimestamp = await algorand.network.getLatestTimestamp()
123+
const targetTimestamp = currentTimestamp - 60n // 1 minute in the past
124+
125+
const startTime = Date.now()
126+
await algorand.network.waitUntilTimestamp(targetTimestamp)
127+
const elapsed = Date.now() - startTime
128+
129+
// Should return almost immediately (under 1 second)
130+
expect(elapsed).toBeLessThan(1000)
131+
})
132+
133+
it('should wait for target timestamp within 1 minute', async () => {
134+
const currentTimestamp = await algorand.network.getLatestTimestamp()
135+
// Wait for 30 seconds into the future
136+
const targetTimestamp = currentTimestamp + 30n
137+
138+
const startTime = Date.now()
139+
await algorand.network.waitUntilTimestamp(targetTimestamp)
140+
const elapsed = Date.now() - startTime
141+
142+
const finalTimestamp = await algorand.network.getLatestTimestamp()
143+
expect(finalTimestamp).toBeGreaterThanOrEqual(targetTimestamp)
144+
145+
// Should complete within 1 minute
146+
expect(elapsed).toBeLessThan(60_000)
147+
}, 60_000)
148+
149+
it('should wait for target timestamp that takes more than 1 minute', async () => {
150+
const currentTimestamp = await algorand.network.getLatestTimestamp()
151+
// Wait for 75 seconds into the future
152+
const targetTimestamp = currentTimestamp + 75n
153+
154+
const startTime = Date.now()
155+
await algorand.network.waitUntilTimestamp(targetTimestamp)
156+
const elapsed = Date.now() - startTime
157+
158+
const finalTimestamp = await algorand.network.getLatestTimestamp()
159+
expect(finalTimestamp).toBeGreaterThanOrEqual(targetTimestamp)
160+
161+
// Should take more than 1 minute but complete successfully
162+
expect(elapsed).toBeGreaterThan(60_000)
163+
}, 120_000)
164+
})
165+
})

0 commit comments

Comments
 (0)