diff --git a/rust/src/transaction_builder.rs b/rust/src/transaction_builder.rs index 67dfe7c..8460181 100644 --- a/rust/src/transaction_builder.rs +++ b/rust/src/transaction_builder.rs @@ -155,6 +155,10 @@ impl UnsignedTransaction { max_fee: u128, priority_fee_bips: u64, gas_limit: Option, + /// Uniqueness generation value. Defaults to the current unix timestamp + /// in milliseconds, giving a ~5-second deduplication window with the + /// sequencer's default 5000-generation window. + generation: Option, client: &Client, ) -> SDKResult { // Check whether the call message was part of the schema validation. @@ -163,16 +167,14 @@ impl UnsignedTransaction { } let runtime_call = RuntimeCall::Exchange(call_message); - // Microseconds, not milliseconds: the sequencer's uniqueness check - // compares against `latest_known_generation` which is stored in μs - // (16-digit unix-time value). A ms timestamp (13 digits) is treated - // as ancient (~1000x older than the cutoff) and rejected with - // CheckUniquenessFailed. - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(|_| SDKError::SystemTimeError)? - .as_micros() as u64; - let uniqueness = UniquenessData::Generation(timestamp); + let generation = match generation { + Some(g) => g, + None => SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|_| SDKError::SystemTimeError)? + .as_millis() as u64, + }; + let uniqueness = UniquenessData::Generation(generation); let details = TxDetails { chain_id: client.chain_id(), max_fee: Amount(max_fee), @@ -348,6 +350,7 @@ impl Transaction { max_fee: Option, priority_fee_bips: Option, gas_limit: Option, + generation: Option, signer: Option<&Keypair>, client: &Client, ) -> SDKResult { @@ -363,6 +366,7 @@ impl Transaction { .max_fee(max_fee) .priority_fee_bips(priority_fee_bips) .maybe_gas_limit(gas_limit) + .maybe_generation(generation) .client(client) .build()?; diff --git a/wasm/README.md b/wasm/README.md index 7cc63cc..e59d68e 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -111,6 +111,18 @@ const tx = Transaction.builder() await client.sendTransaction(tx); ``` +By default the uniqueness generation is a millisecond unix timestamp, giving +a ~5-second deduplication window. Override with `.generation()` to pass any +value — for example a microsecond timestamp for a ~5ms window: + +```typescript +const tx = Transaction.builder() + .callMessage(msg) + .generation(BigInt(Date.now()) * 1000n) // microseconds + .signer(keypair) + .send(client); +``` + ### External Signing For hardware wallets or external signing services that can sign the standard diff --git a/wasm/src/transaction_builder.rs b/wasm/src/transaction_builder.rs index 74094cf..31124df 100644 --- a/wasm/src/transaction_builder.rs +++ b/wasm/src/transaction_builder.rs @@ -382,6 +382,7 @@ impl WasmTransactionEntry { /// - `maxFee` - Maximum fee willing to pay (in base units) /// - `priorityFeeBips` - Priority fee in basis points /// - `gasLimit` - Optional gas limit [ref_time, proof_size] +/// - `generation` - Uniqueness generation value (default: current unix timestamp in milliseconds) /// - `signer` - Keypair to sign the transaction (not required for `buildUnsigned`) #[wasm_bindgen(js_name = TransactionBuilder)] pub struct WasmTransactionBuilder { @@ -389,6 +390,7 @@ pub struct WasmTransactionBuilder { max_fee: Option, priority_fee_bips: Option, gas_limit: Option<[u64; 2]>, + generation: Option, signer: Option, } @@ -399,6 +401,7 @@ impl WasmTransactionBuilder { max_fee: None, priority_fee_bips: None, gas_limit: None, + generation: None, signer: None, } } @@ -436,6 +439,18 @@ impl WasmTransactionBuilder { self } + /// Override the uniqueness generation value. + /// + /// Defaults to the current unix timestamp in milliseconds, giving a + /// ~5-second deduplication window with the sequencer's 5000-generation window. + /// Pass a microsecond timestamp for a ~5ms window, or any other value as needed. + /// @param {bigint} generation - The generation value to use. + /// @returns {TransactionBuilder} + pub fn generation(mut self, generation: u64) -> WasmTransactionBuilder { + self.generation = Some(generation); + self + } + /// Set the keypair used to sign this transaction. pub fn signer(mut self, keypair: WasmKeypair) -> WasmTransactionBuilder { self.signer = Some(keypair); @@ -470,6 +485,7 @@ impl WasmTransactionBuilder { .max_fee(max_fee) .priority_fee_bips(priority_fee_bips) .maybe_gas_limit(gas_limit) + .maybe_generation(self.generation) .client(&client.inner) .build()?; @@ -489,6 +505,7 @@ impl WasmTransactionBuilder { .maybe_max_fee(max_fee) .maybe_priority_fee_bips(self.priority_fee_bips) .maybe_gas_limit(gas_limit) + .maybe_generation(self.generation) .maybe_signer(signer_ref) .client(&client.inner) .build()?;