Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions rust/src/transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ impl UnsignedTransaction {
max_fee: u128,
priority_fee_bips: u64,
gas_limit: Option<Gas>,
/// 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<u64>,
client: &Client,
) -> SDKResult<UnsignedTransaction> {
// Check whether the call message was part of the schema validation.
Expand All @@ -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),
Expand Down Expand Up @@ -348,6 +350,7 @@ impl Transaction {
max_fee: Option<u128>,
priority_fee_bips: Option<u64>,
gas_limit: Option<Gas>,
generation: Option<u64>,
signer: Option<&Keypair>,
client: &Client,
) -> SDKResult<SignedTransaction> {
Expand All @@ -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()?;

Expand Down
12 changes: 12 additions & 0 deletions wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The example is missing await before the builder chain, so tx is a Promise instead of the resolved result from send(client).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At wasm/README.md, line 119:

<comment>The example is missing `await` before the builder chain, so `tx` is a `Promise` instead of the resolved result from `send(client)`.</comment>

<file context>
@@ -111,6 +111,18 @@ const tx = Transaction.builder()
+value — for example a microsecond timestamp for a ~5ms window:
+
+```typescript
+const tx = Transaction.builder()
+    .callMessage(msg)
+    .generation(BigInt(Date.now()) * 1000n)  // microseconds
</file context>
Suggested change
const tx = Transaction.builder()
const response = await 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
Expand Down
17 changes: 17 additions & 0 deletions wasm/src/transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,13 +382,15 @@ 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 {
call_message: Option<WasmCallMessage>,
max_fee: Option<u64>,
priority_fee_bips: Option<u64>,
gas_limit: Option<[u64; 2]>,
generation: Option<u64>,
signer: Option<WasmKeypair>,
}

Expand All @@ -399,6 +401,7 @@ impl WasmTransactionBuilder {
max_fee: None,
priority_fee_bips: None,
gas_limit: None,
generation: None,
signer: None,
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()?;

Expand All @@ -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()?;
Expand Down