@towns-labs/relayer 2.0.13 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/README.md CHANGED
@@ -1 +1 @@
1
- This folder contains the built output assets for the worker "relayer-worker" generated at 2026-02-12T00:44:04.759Z.
1
+ This folder contains the built output assets for the worker "relayer-worker" generated at 2026-02-16T21:41:19.331Z.
package/dist/index.js CHANGED
@@ -23663,6 +23663,7 @@ var ACCOUNT_NOT_DELEGATED = -32009;
23663
23663
  var QUOTE_EXPIRED = -32010;
23664
23664
  var INVALID_QUOTE_SIGNATURE = -32011;
23665
23665
  var PAYMENT_EXCEEDS_MAX = -32012;
23666
+ var DRAFT_CONFLICT = -32013;
23666
23667
  var ERROR_MESSAGES = {
23667
23668
  [PARSE_ERROR]: "Parse error",
23668
23669
  [INVALID_REQUEST]: "Invalid Request",
@@ -23681,7 +23682,8 @@ var ERROR_MESSAGES = {
23681
23682
  [ACCOUNT_NOT_DELEGATED]: "Account not delegated",
23682
23683
  [QUOTE_EXPIRED]: "Quote expired",
23683
23684
  [INVALID_QUOTE_SIGNATURE]: "Invalid quote signature",
23684
- [PAYMENT_EXCEEDS_MAX]: "Payment amount exceeds maximum"
23685
+ [PAYMENT_EXCEEDS_MAX]: "Payment amount exceeds maximum",
23686
+ [DRAFT_CONFLICT]: "Draft conflict"
23685
23687
  };
23686
23688
  var RpcError2 = class extends Error {
23687
23689
  static {
@@ -23801,7 +23803,7 @@ function isEip7702Delegated(code) {
23801
23803
  }
23802
23804
  __name(isEip7702Delegated, "isEip7702Delegated");
23803
23805
 
23804
- // src/services/relayer.ts
23806
+ // src/rpc/schema/intentTypes.ts
23805
23807
  var INTENT_TYPES = {
23806
23808
  Intent: [
23807
23809
  { name: "multichain", type: "bool" },
@@ -23823,60 +23825,39 @@ var INTENT_TYPES = {
23823
23825
  { name: "data", type: "bytes" }
23824
23826
  ]
23825
23827
  };
23828
+
23829
+ // src/services/relayer.ts
23826
23830
  function createIntentNonceProvider(durableObject, chainId) {
23827
23831
  const durableObjectNameFor = /* @__PURE__ */ __name((eoa) => `${chainId}:${eoa.toLowerCase()}`, "durableObjectNameFor");
23832
+ const getStubForEoa = /* @__PURE__ */ __name((eoa) => {
23833
+ const id = durableObject.idFromName(durableObjectNameFor(eoa));
23834
+ return durableObject.get(id);
23835
+ }, "getStubForEoa");
23836
+ const postToNonceDo = /* @__PURE__ */ __name(async (eoa, path, body) => {
23837
+ const stub = getStubForEoa(eoa);
23838
+ return stub.fetch(
23839
+ new Request(`http://do/${path}`, {
23840
+ method: "POST",
23841
+ headers: { "Content-Type": "application/json" },
23842
+ body: JSON.stringify(body)
23843
+ })
23844
+ );
23845
+ }, "postToNonceDo");
23828
23846
  return {
23829
- async acquireNonce(eoa, seqKey = 0n) {
23830
- const id = durableObject.idFromName(durableObjectNameFor(eoa));
23831
- const stub = durableObject.get(id);
23832
- const response = await stub.fetch(
23833
- new Request("http://do/acquire", {
23834
- method: "POST",
23835
- headers: { "Content-Type": "application/json" },
23836
- body: JSON.stringify({ seqKey: seqKey.toString() })
23837
- })
23838
- );
23839
- if (!response.ok) {
23840
- const error48 = await response.json();
23841
- throw new Error(`Failed to acquire intent nonce: ${error48.error}`);
23842
- }
23843
- const data = await response.json();
23844
- return BigInt(data.nonce);
23845
- },
23846
- async acquireNonceSynced(eoa, seqKey, onChainSeq) {
23847
- const id = durableObject.idFromName(durableObjectNameFor(eoa));
23848
- const stub = durableObject.get(id);
23849
- const response = await stub.fetch(
23850
- new Request("http://do/acquire_synced", {
23851
- method: "POST",
23852
- headers: { "Content-Type": "application/json" },
23853
- body: JSON.stringify({
23854
- seqKey: seqKey.toString(),
23855
- onChainSeq: onChainSeq.toString()
23856
- })
23857
- })
23858
- );
23859
- if (!response.ok) {
23860
- const error48 = await response.json();
23861
- throw new Error(`Failed to acquire synced intent nonce: ${error48.error}`);
23862
- }
23863
- const data = await response.json();
23864
- return { nonce: BigInt(data.nonce), synced: data.synced };
23865
- },
23866
23847
  async acquireOrGetDraft(eoa, seqKey, onChainSeq, prepareKey) {
23867
- const id = durableObject.idFromName(durableObjectNameFor(eoa));
23868
- const stub = durableObject.get(id);
23869
- const response = await stub.fetch(
23870
- new Request("http://do/acquire_or_get_draft", {
23871
- method: "POST",
23872
- headers: { "Content-Type": "application/json" },
23873
- body: JSON.stringify({
23874
- seqKey: seqKey.toString(),
23875
- onChainSeq: onChainSeq.toString(),
23876
- draftKey: prepareKey
23877
- })
23878
- })
23879
- );
23848
+ const response = await postToNonceDo(eoa, "acquire_or_get_draft", {
23849
+ seqKey: seqKey.toString(),
23850
+ onChainSeq: onChainSeq.toString(),
23851
+ draftKey: prepareKey
23852
+ });
23853
+ if (response.status === 409) {
23854
+ const body = await response.json();
23855
+ return {
23856
+ conflict: true,
23857
+ error: body.error,
23858
+ conflictDraftId: body.conflictDraftId
23859
+ };
23860
+ }
23880
23861
  if (!response.ok) {
23881
23862
  const error48 = await response.json();
23882
23863
  throw new Error(`Failed to acquire intent draft: ${error48.error}`);
@@ -23891,55 +23872,16 @@ function createIntentNonceProvider(durableObject, chainId) {
23891
23872
  };
23892
23873
  },
23893
23874
  async markSubmitted(eoa, seqKey, draftId) {
23894
- const id = durableObject.idFromName(durableObjectNameFor(eoa));
23895
- const stub = durableObject.get(id);
23896
- const response = await stub.fetch(
23897
- new Request("http://do/mark_submitted", {
23898
- method: "POST",
23899
- headers: { "Content-Type": "application/json" },
23900
- body: JSON.stringify({ seqKey: seqKey.toString(), draftId })
23901
- })
23902
- );
23875
+ const response = await postToNonceDo(eoa, "mark_submitted", {
23876
+ seqKey: seqKey.toString(),
23877
+ draftId
23878
+ });
23903
23879
  if (!response.ok) {
23904
23880
  const error48 = await response.json();
23905
23881
  throw new Error(`Failed to mark intent draft submitted: ${error48.error}`);
23906
23882
  }
23907
23883
  const data = await response.json();
23908
23884
  return data.status;
23909
- },
23910
- async cancelDraft(eoa, seqKey, draftId) {
23911
- const id = durableObject.idFromName(durableObjectNameFor(eoa));
23912
- const stub = durableObject.get(id);
23913
- const response = await stub.fetch(
23914
- new Request("http://do/cancel_draft", {
23915
- method: "POST",
23916
- headers: { "Content-Type": "application/json" },
23917
- body: JSON.stringify({ seqKey: seqKey.toString(), draftId })
23918
- })
23919
- );
23920
- if (!response.ok) {
23921
- const error48 = await response.json();
23922
- throw new Error(`Failed to cancel intent draft: ${error48.error}`);
23923
- }
23924
- const data = await response.json();
23925
- return data.status;
23926
- },
23927
- async syncNonce(eoa, seqKey, confirmedSeq) {
23928
- const id = durableObject.idFromName(durableObjectNameFor(eoa));
23929
- const stub = durableObject.get(id);
23930
- const response = await stub.fetch(
23931
- new Request("http://do/sync", {
23932
- method: "POST",
23933
- headers: { "Content-Type": "application/json" },
23934
- body: JSON.stringify({
23935
- seqKey: seqKey.toString(),
23936
- confirmedSeq: confirmedSeq.toString()
23937
- })
23938
- })
23939
- );
23940
- if (!response.ok) {
23941
- console.warn("Failed to sync intent nonce for:", eoa);
23942
- }
23943
23885
  }
23944
23886
  };
23945
23887
  }
@@ -23953,6 +23895,26 @@ var DEFAULT_GAS_CONFIG = {
23953
23895
  txGasBuffer: 0n,
23954
23896
  allowSimulationFallback: false
23955
23897
  };
23898
+ var DEFAULT_INTENT_EXPIRY_SECONDS = 3600n;
23899
+ var MAX_INTENT_EXPIRY_SECONDS_FROM_NOW = 365n * 24n * 60n * 60n;
23900
+ var MILLISECONDS_EPOCH_THRESHOLD = 1000000000000n;
23901
+ function resolveIntentExpirySeconds(rawExpiry, nowSeconds = BigInt(Math.floor(Date.now() / 1e3))) {
23902
+ if (!rawExpiry) {
23903
+ return nowSeconds + DEFAULT_INTENT_EXPIRY_SECONDS;
23904
+ }
23905
+ const expiry = BigInt(rawExpiry);
23906
+ const maxSupportedExpiry = nowSeconds + MAX_INTENT_EXPIRY_SECONDS_FROM_NOW;
23907
+ if (expiry > maxSupportedExpiry) {
23908
+ if (expiry >= MILLISECONDS_EPOCH_THRESHOLD) {
23909
+ throw new Error(
23910
+ `Invalid expiry: ${rawExpiry} appears to be milliseconds; use unix seconds`
23911
+ );
23912
+ }
23913
+ throw new Error(`Invalid expiry: ${rawExpiry} is too far in the future`);
23914
+ }
23915
+ return expiry;
23916
+ }
23917
+ __name(resolveIntentExpirySeconds, "resolveIntentExpirySeconds");
23956
23918
  function isPaymentEnabled(payer, paymentToken) {
23957
23919
  return payer !== zeroAddress && paymentToken !== zeroAddress;
23958
23920
  }
@@ -23961,6 +23923,14 @@ function calculateCombinedGas(simulationGas, gasConfig, paymentEnabled) {
23961
23923
  return simulationGas + gasConfig.intentGasBuffer + (paymentEnabled ? gasConfig.paymentGasBuffer : 0n);
23962
23924
  }
23963
23925
  __name(calculateCombinedGas, "calculateCombinedGas");
23926
+ function normalizeCalls(calls) {
23927
+ return calls.map((call2) => ({
23928
+ to: call2.to,
23929
+ value: call2.value ? BigInt(call2.value) : 0n,
23930
+ data: call2.data ?? "0x"
23931
+ }));
23932
+ }
23933
+ __name(normalizeCalls, "normalizeCalls");
23964
23934
  function extractRevertData(error48) {
23965
23935
  if (error48 instanceof BaseError2) {
23966
23936
  const rawError = error48.walk((e) => e instanceof RawContractError);
@@ -24047,12 +24017,20 @@ var RelayerService = class {
24047
24017
  async acquireNonceFromProvider(eoa, seqKey, prepareKey) {
24048
24018
  const onChainSeq = await this.fetchOnChainSeq(eoa, seqKey);
24049
24019
  const safeOnChainSeq = onChainSeq ?? 0n;
24050
- const { nonce, draftId, expiresAtMs, fromCache } = await this.intentNonceProvider.acquireOrGetDraft(
24020
+ const result = await this.intentNonceProvider.acquireOrGetDraft(
24051
24021
  eoa,
24052
24022
  seqKey,
24053
24023
  safeOnChainSeq,
24054
24024
  prepareKey
24055
24025
  );
24026
+ if ("conflict" in result) {
24027
+ return {
24028
+ success: false,
24029
+ error: result.error,
24030
+ conflictDraftId: result.conflictDraftId
24031
+ };
24032
+ }
24033
+ const { nonce, draftId, expiresAtMs, fromCache } = result;
24056
24034
  if (onChainSeq === null) {
24057
24035
  this.logger.debug(
24058
24036
  { eoa, nonce: nonce.toString(), draftId },
@@ -24119,11 +24097,7 @@ var RelayerService = class {
24119
24097
  errorCode: "DELEGATION_PENDING"
24120
24098
  };
24121
24099
  }
24122
- const calls = request.calls.map((c2) => ({
24123
- to: c2.to,
24124
- value: c2.value ? BigInt(c2.value) : 0n,
24125
- data: c2.data ?? "0x"
24126
- }));
24100
+ const calls = normalizeCalls(request.calls);
24127
24101
  const executionData = encodeAbiParameters(
24128
24102
  [
24129
24103
  {
@@ -24137,6 +24111,19 @@ var RelayerService = class {
24137
24111
  ],
24138
24112
  [calls]
24139
24113
  );
24114
+ const nowSeconds = BigInt(Math.floor(Date.now() / 1e3));
24115
+ const expiry = resolveIntentExpirySeconds(request.expiry, nowSeconds);
24116
+ let signature = request.signature ?? "0x";
24117
+ if (request.sessionKey && !request.signature) {
24118
+ const keyHash = keccak256(
24119
+ encodeAbiParameters(parseAbiParameters("uint8, bytes32"), [
24120
+ 0,
24121
+ // secp256k1
24122
+ keccak256(request.sessionKey)
24123
+ ])
24124
+ );
24125
+ signature = concat([`0x${"00".repeat(65)}`, keyHash, "0x00"]);
24126
+ }
24140
24127
  const intent = {
24141
24128
  eoa: request.eoa,
24142
24129
  executionData,
@@ -24149,14 +24136,14 @@ var RelayerService = class {
24149
24136
  encodedPreCalls: request.encodedPreCalls ?? [],
24150
24137
  encodedFundTransfers: request.encodedFundTransfers ?? [],
24151
24138
  settler: request.settler ?? zeroAddress,
24152
- expiry: BigInt(request.expiry ?? Math.floor(Date.now() / 1e3) + 3600),
24139
+ expiry,
24153
24140
  isMultichain: request.isMultichain ?? false,
24154
24141
  funder: request.funder ?? zeroAddress,
24155
24142
  funderSignature: request.funderSignature ?? "0x",
24156
24143
  settlerContext: request.settlerContext ?? "0x",
24157
24144
  paymentAmount: BigInt(request.paymentAmount ?? "0"),
24158
24145
  paymentRecipient: request.paymentRecipient ?? zeroAddress,
24159
- signature: request.signature ?? "0x",
24146
+ signature,
24160
24147
  paymentSignature: request.paymentSignature ?? "0x",
24161
24148
  supportedAccountImplementation: request.supportedAccountImplementation ?? zeroAddress
24162
24149
  };
@@ -24270,10 +24257,15 @@ var RelayerService = class {
24270
24257
  request.prepareKey
24271
24258
  );
24272
24259
  if (!nonceResult.success) {
24273
- return { success: false, error: nonceResult.error };
24260
+ return {
24261
+ success: false,
24262
+ error: nonceResult.error,
24263
+ conflictDraftId: nonceResult.conflictDraftId
24264
+ };
24274
24265
  }
24275
24266
  const nonce = nonceResult.nonce;
24276
- const expiry = request.expiry ? BigInt(request.expiry) : BigInt(Math.floor(Date.now() / 1e3) + 3600);
24267
+ const nowSeconds = BigInt(Math.floor(Date.now() / 1e3));
24268
+ const expiry = resolveIntentExpirySeconds(request.expiry, nowSeconds);
24277
24269
  const encodedPreCalls = request.encodedPreCalls ?? [];
24278
24270
  const encodedFundTransfers = request.encodedFundTransfers ?? [];
24279
24271
  const settler = request.settler ?? zeroAddress;
@@ -24281,11 +24273,7 @@ var RelayerService = class {
24281
24273
  const paymentToken = request.paymentToken ?? zeroAddress;
24282
24274
  const paymentMaxAmount = BigInt(request.paymentMaxAmount ?? "0");
24283
24275
  const paymentEnabled = isPaymentEnabled(payer, paymentToken);
24284
- const calls = request.calls.map((c2) => ({
24285
- to: c2.to,
24286
- value: c2.value ? BigInt(c2.value) : 0n,
24287
- data: c2.data ?? "0x"
24288
- }));
24276
+ const calls = normalizeCalls(request.calls);
24289
24277
  const simulateResult = await this.simulateIntent({
24290
24278
  eoa: request.eoa,
24291
24279
  calls: request.calls,
@@ -24296,7 +24284,8 @@ var RelayerService = class {
24296
24284
  settler: request.settler,
24297
24285
  payer: request.payer,
24298
24286
  paymentToken: request.paymentToken,
24299
- paymentMaxAmount: request.paymentMaxAmount
24287
+ paymentMaxAmount: request.paymentMaxAmount,
24288
+ sessionKey: request.sessionKey
24300
24289
  });
24301
24290
  const simulationGas = simulateResult.gasUsed ? BigInt(simulateResult.gasUsed) : 0n;
24302
24291
  const simulationSucceeded = simulateResult.success && simulationGas > 0n;
@@ -24557,30 +24546,9 @@ function hashQuotes(signedQuotes, config2) {
24557
24546
  settler: quote.intent.settler ?? zeroAddress,
24558
24547
  expiry: BigInt(quote.intent.expiry || "0")
24559
24548
  };
24560
- const INTENT_TYPES_FOR_HASH = {
24561
- Intent: [
24562
- { name: "multichain", type: "bool" },
24563
- { name: "eoa", type: "address" },
24564
- { name: "calls", type: "Call[]" },
24565
- { name: "nonce", type: "uint256" },
24566
- { name: "payer", type: "address" },
24567
- { name: "paymentToken", type: "address" },
24568
- { name: "paymentMaxAmount", type: "uint256" },
24569
- { name: "combinedGas", type: "uint256" },
24570
- { name: "encodedPreCalls", type: "bytes[]" },
24571
- { name: "encodedFundTransfers", type: "bytes[]" },
24572
- { name: "settler", type: "address" },
24573
- { name: "expiry", type: "uint256" }
24574
- ],
24575
- Call: [
24576
- { name: "to", type: "address" },
24577
- { name: "value", type: "uint256" },
24578
- { name: "data", type: "bytes" }
24579
- ]
24580
- };
24581
24549
  const intentDigest = hashTypedData({
24582
24550
  domain: domain2,
24583
- types: INTENT_TYPES_FOR_HASH,
24551
+ types: INTENT_TYPES,
24584
24552
  primaryType: "Intent",
24585
24553
  message: intentMessage
24586
24554
  });
@@ -24637,7 +24605,7 @@ function extractIntentFromContext(context) {
24637
24605
  __name(extractIntentFromContext, "extractIntentFromContext");
24638
24606
  async function validateQuote(signedQuotes, env) {
24639
24607
  const currentTime = Math.floor(Date.now() / 1e3);
24640
- if (typeof signedQuotes.ttl !== "number" || signedQuotes.ttl < currentTime) {
24608
+ if (typeof signedQuotes.ttl !== "number" || signedQuotes.ttl <= currentTime) {
24641
24609
  return new RpcError2(
24642
24610
  QUOTE_EXPIRED,
24643
24611
  `Quote expired at ${signedQuotes.ttl}, current time is ${currentTime}`
@@ -40053,6 +40021,7 @@ async function handlePrepareCalls(params, ctx) {
40053
40021
  const nonce = meta3?.nonce;
40054
40022
  const seqKey = meta3?.seq_key;
40055
40023
  const prepareKey = meta3?.prepare_key;
40024
+ const expiry = meta3?.expiry;
40056
40025
  const payer = meta3?.fee_payer;
40057
40026
  const paymentToken = meta3?.fee_token;
40058
40027
  const paymentMaxAmount = meta3?.fee_max_amount;
@@ -40071,13 +40040,27 @@ async function handlePrepareCalls(params, ctx) {
40071
40040
  calls: normalizedCalls,
40072
40041
  nonce,
40073
40042
  seqKey,
40043
+ expiry,
40074
40044
  settler,
40075
40045
  payer,
40076
40046
  paymentToken,
40077
40047
  paymentMaxAmount,
40078
- prepareKey
40048
+ prepareKey,
40049
+ sessionKey: typedParams.session_key
40079
40050
  });
40080
40051
  if (!result.success || !result.typedData || !result.digest) {
40052
+ if (result.conflictDraftId) {
40053
+ throw new RpcError2(DRAFT_CONFLICT, result.error ?? "Draft conflict", {
40054
+ conflictDraftId: result.conflictDraftId
40055
+ });
40056
+ }
40057
+ const rawError = result.error ?? "Failed to prepare calls";
40058
+ if (rawError.toLowerCase().startsWith("simulation failed")) {
40059
+ const cause = rawError.replace(/^simulation failed:\s*/i, "").trim();
40060
+ throw new RpcError2(SIMULATION_FAILED, "Simulation failed", {
40061
+ cause: cause.length > 0 ? cause : void 0
40062
+ });
40063
+ }
40081
40064
  throw new RpcError2(INTERNAL_ERROR, result.error ?? "Failed to prepare calls");
40082
40065
  }
40083
40066
  const feeConfig = getFeeConfig(env);
@@ -40212,14 +40195,6 @@ async function handlePrepareCalls(params, ctx) {
40212
40195
  message[key] = value;
40213
40196
  }
40214
40197
  }
40215
- let callKey;
40216
- if (typedParams.key) {
40217
- callKey = {
40218
- type: typedParams.key.type,
40219
- publicKey: typedParams.key.public_key,
40220
- prehash: typedParams.key.prehash ?? false
40221
- };
40222
- }
40223
40198
  return {
40224
40199
  context: preparedContext,
40225
40200
  digest: result.digest,
@@ -40235,7 +40210,6 @@ async function handlePrepareCalls(params, ctx) {
40235
40210
  feeTotals: {},
40236
40211
  assetDiffs: {}
40237
40212
  },
40238
- key: callKey,
40239
40213
  signature: signedQuotes.signature
40240
40214
  };
40241
40215
  }
@@ -45439,11 +45413,17 @@ __name(isPendingTransactionIdUniqueConstraintError, "isPendingTransactionIdUniqu
45439
45413
 
45440
45414
  // src/durable-objects/signer.do.ts
45441
45415
  var DEFAULT_MAX_PENDING = 16;
45442
- var BALANCE_CHECK_INTERVAL_MS = 6e4;
45416
+ var BALANCE_CHECK_INTERVAL_MS = 3e5;
45443
45417
  var STALE_TX_THRESHOLD_MS = 5 * 60 * 1e3;
45444
45418
  var DEFAULT_INTENT_EXPIRY_BUFFER_SECONDS = 30;
45445
45419
  var DUPLICATE_TX_WAIT_MS = 2e3;
45446
45420
  var DUPLICATE_TX_WAIT_POLL_MS = 100;
45421
+ function mapStoredTxStatusToPublicStatus(storedStatus) {
45422
+ if (storedStatus === "confirmed") return "confirmed";
45423
+ if (storedStatus === "failed" || storedStatus === "stuck") return "failed";
45424
+ return "pending";
45425
+ }
45426
+ __name(mapStoredTxStatusToPublicStatus, "mapStoredTxStatusToPublicStatus");
45447
45427
  function isIntentExpired(expiryTimestamp, bufferSeconds = DEFAULT_INTENT_EXPIRY_BUFFER_SECONDS) {
45448
45428
  const expiry = typeof expiryTimestamp === "string" ? BigInt(expiryTimestamp) : expiryTimestamp;
45449
45429
  const currentTime = BigInt(Math.floor(Date.now() / 1e3));
@@ -45878,11 +45858,12 @@ var SignerDO = class extends DurableObject {
45878
45858
  }
45879
45859
  } catch {
45880
45860
  }
45861
+ const fallbackStatus = mapStoredTxStatusToPublicStatus(status);
45881
45862
  return {
45882
45863
  txId,
45883
45864
  txHash,
45884
45865
  chainId,
45885
- status: "pending",
45866
+ status: fallbackStatus,
45886
45867
  submittedAt: sentAt
45887
45868
  };
45888
45869
  }
@@ -45954,7 +45935,7 @@ var SignerDO = class extends DurableObject {
45954
45935
  await this.ensureInitialized();
45955
45936
  const rows = this.sql.exec(
45956
45937
  `
45957
- SELECT address, paused, last_balance_check, balance_wei
45938
+ SELECT address, chain_id, paused, last_balance_check, balance_wei
45958
45939
  FROM signer_state WHERE id = 1
45959
45940
  `
45960
45941
  ).toArray();
@@ -47754,6 +47735,15 @@ var IntentNonceDO = class extends DurableObject4 {
47754
47735
  draftKey,
47755
47736
  draftTtlMs: coerceDraftTtlMs(draftTtlMs)
47756
47737
  });
47738
+ if ("error" in result) {
47739
+ return Response.json(
47740
+ {
47741
+ error: result.error,
47742
+ conflictDraftId: result.conflictDraftId
47743
+ },
47744
+ { status: 409 }
47745
+ );
47746
+ }
47757
47747
  return Response.json({
47758
47748
  nonce: result.nonce.toString(),
47759
47749
  draftId: result.draftId,
@@ -47935,6 +47925,14 @@ var IntentNonceDO = class extends DurableObject4 {
47935
47925
  this.deleteExpiredDraftForSeqKey(key, nowMs);
47936
47926
  const existingDraft = this.getDraftForSeqKey(key);
47937
47927
  if (existingDraft !== null) {
47928
+ const incomingDraftKey = options.draftKey ?? null;
47929
+ const existingDraftKey = existingDraft.draft_key;
47930
+ if (incomingDraftKey !== existingDraftKey) {
47931
+ return {
47932
+ error: "draft already exists for seqKey with a different draftKey; complete or cancel the active request first",
47933
+ conflictDraftId: existingDraft.draft_id
47934
+ };
47935
+ }
47938
47936
  return {
47939
47937
  nonce: BigInt(existingDraft.nonce),
47940
47938
  draftId: existingDraft.draft_id,