@towns-labs/relayer 3.4.1 → 4.1.0
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 +1 -1
- package/dist/index.js +469 -62
- package/dist/index.js.map +3 -3
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -24114,6 +24114,13 @@ function getSeqKeyForDraftMark(intentNonce, seqKeyFromContext) {
|
|
|
24114
24114
|
}
|
|
24115
24115
|
}
|
|
24116
24116
|
__name(getSeqKeyForDraftMark, "getSeqKeyForDraftMark");
|
|
24117
|
+
function buildBundleTrackingUnavailableError() {
|
|
24118
|
+
return new RpcError2(
|
|
24119
|
+
SERVICE_UNAVAILABLE,
|
|
24120
|
+
"Intent submitted but bundle tracking unavailable; retry status lookup later"
|
|
24121
|
+
);
|
|
24122
|
+
}
|
|
24123
|
+
__name(buildBundleTrackingUnavailableError, "buildBundleTrackingUnavailableError");
|
|
24117
24124
|
async function handleSendPreparedCalls(params, ctx) {
|
|
24118
24125
|
const env = ctx.env;
|
|
24119
24126
|
const typedParams = unwrapParams(params);
|
|
@@ -24201,7 +24208,7 @@ async function handleSendPreparedCalls(params, ctx) {
|
|
|
24201
24208
|
try {
|
|
24202
24209
|
const bundleStatusId = env.BUNDLE_STATUS_DO.idFromName(`bundle-status-${chainId}`);
|
|
24203
24210
|
const bundleStatus = env.BUNDLE_STATUS_DO.get(bundleStatusId);
|
|
24204
|
-
await bundleStatus.fetch("http://do/add_bundle_tx", {
|
|
24211
|
+
const addBundleResponse = await bundleStatus.fetch("http://do/add_bundle_tx", {
|
|
24205
24212
|
method: "POST",
|
|
24206
24213
|
headers: { "Content-Type": "application/json" },
|
|
24207
24214
|
body: JSON.stringify({
|
|
@@ -24210,30 +24217,87 @@ async function handleSendPreparedCalls(params, ctx) {
|
|
|
24210
24217
|
signerName: result.signerName
|
|
24211
24218
|
})
|
|
24212
24219
|
});
|
|
24220
|
+
if (!addBundleResponse.ok) {
|
|
24221
|
+
logger.error(
|
|
24222
|
+
{
|
|
24223
|
+
category: "bundle_tracking_persist_failed",
|
|
24224
|
+
bundleId,
|
|
24225
|
+
chainId,
|
|
24226
|
+
txId: tx.id,
|
|
24227
|
+
signerName: result.signerName,
|
|
24228
|
+
status: addBundleResponse.status,
|
|
24229
|
+
statusText: addBundleResponse.statusText
|
|
24230
|
+
},
|
|
24231
|
+
"bundle tracking persistence failed"
|
|
24232
|
+
);
|
|
24233
|
+
throw buildBundleTrackingUnavailableError();
|
|
24234
|
+
}
|
|
24213
24235
|
if ("quote" in context && context.quote?.quotes?.length) {
|
|
24214
24236
|
const quote = context.quote.quotes[0];
|
|
24215
24237
|
const telemetry = quote?.telemetry;
|
|
24216
24238
|
if (telemetry?.combinedGas || telemetry?.simulationGas || telemetry?.txGas) {
|
|
24217
|
-
|
|
24218
|
-
|
|
24219
|
-
|
|
24220
|
-
|
|
24221
|
-
|
|
24222
|
-
|
|
24223
|
-
|
|
24224
|
-
|
|
24225
|
-
|
|
24226
|
-
|
|
24227
|
-
|
|
24228
|
-
|
|
24229
|
-
|
|
24239
|
+
try {
|
|
24240
|
+
const telemetryResponse = await bundleStatus.fetch(
|
|
24241
|
+
"http://do/upsert_bundle_telemetry",
|
|
24242
|
+
{
|
|
24243
|
+
method: "POST",
|
|
24244
|
+
headers: { "Content-Type": "application/json" },
|
|
24245
|
+
body: JSON.stringify({
|
|
24246
|
+
bundleId,
|
|
24247
|
+
chainId,
|
|
24248
|
+
eoa: intent.eoa,
|
|
24249
|
+
paymentEnabled: telemetry.paymentEnabled ?? false,
|
|
24250
|
+
simulationGas: telemetry.simulationGas,
|
|
24251
|
+
combinedGas: telemetry.combinedGas,
|
|
24252
|
+
txGas: telemetry.txGas
|
|
24253
|
+
})
|
|
24254
|
+
}
|
|
24255
|
+
);
|
|
24256
|
+
if (!telemetryResponse.ok) {
|
|
24257
|
+
logger.warn(
|
|
24258
|
+
{
|
|
24259
|
+
category: "bundle_telemetry_persist_failed",
|
|
24260
|
+
bundleId,
|
|
24261
|
+
chainId,
|
|
24262
|
+
txId: tx.id,
|
|
24263
|
+
signerName: result.signerName,
|
|
24264
|
+
status: telemetryResponse.status,
|
|
24265
|
+
statusText: telemetryResponse.statusText
|
|
24266
|
+
},
|
|
24267
|
+
"bundle telemetry persistence failed"
|
|
24268
|
+
);
|
|
24269
|
+
}
|
|
24270
|
+
} catch (error48) {
|
|
24271
|
+
logger.warn(
|
|
24272
|
+
{
|
|
24273
|
+
category: "bundle_telemetry_persist_failed",
|
|
24274
|
+
error: getErrorMessage(error48),
|
|
24275
|
+
bundleId,
|
|
24276
|
+
chainId,
|
|
24277
|
+
txId: tx.id,
|
|
24278
|
+
signerName: result.signerName
|
|
24279
|
+
},
|
|
24280
|
+
"failed to persist bundle telemetry"
|
|
24281
|
+
);
|
|
24282
|
+
}
|
|
24230
24283
|
}
|
|
24231
24284
|
}
|
|
24232
24285
|
} catch (error48) {
|
|
24286
|
+
if (error48 instanceof RpcError2) {
|
|
24287
|
+
throw error48;
|
|
24288
|
+
}
|
|
24233
24289
|
logger.warn(
|
|
24234
|
-
{
|
|
24235
|
-
|
|
24290
|
+
{
|
|
24291
|
+
category: "bundle_tracking_persist_failed",
|
|
24292
|
+
error: getErrorMessage(error48),
|
|
24293
|
+
bundleId,
|
|
24294
|
+
chainId,
|
|
24295
|
+
txId: tx.id,
|
|
24296
|
+
signerName: result.signerName
|
|
24297
|
+
},
|
|
24298
|
+
"failed to persist bundle tracking"
|
|
24236
24299
|
);
|
|
24300
|
+
throw buildBundleTrackingUnavailableError();
|
|
24237
24301
|
}
|
|
24238
24302
|
}
|
|
24239
24303
|
return { id: bundleId };
|
|
@@ -24369,12 +24433,13 @@ async function handleBatchSendPreparedCalls(requests, ctx) {
|
|
|
24369
24433
|
);
|
|
24370
24434
|
}
|
|
24371
24435
|
}
|
|
24436
|
+
const bundleTrackingErrors = /* @__PURE__ */ new Map();
|
|
24372
24437
|
if (env.BUNDLE_STATUS_DO) {
|
|
24373
24438
|
const bundleStatusId = env.BUNDLE_STATUS_DO.idFromName(`bundle-status-${batchChainId}`);
|
|
24374
24439
|
const bundleStatus = env.BUNDLE_STATUS_DO.get(bundleStatusId);
|
|
24375
24440
|
for (const req of parsedRequests) {
|
|
24376
24441
|
try {
|
|
24377
|
-
await bundleStatus.fetch("http://do/add_bundle_tx", {
|
|
24442
|
+
const addBundleResponse = await bundleStatus.fetch("http://do/add_bundle_tx", {
|
|
24378
24443
|
method: "POST",
|
|
24379
24444
|
headers: { "Content-Type": "application/json" },
|
|
24380
24445
|
body: JSON.stringify({
|
|
@@ -24383,14 +24448,34 @@ async function handleBatchSendPreparedCalls(requests, ctx) {
|
|
|
24383
24448
|
signerName: result.signerName
|
|
24384
24449
|
})
|
|
24385
24450
|
});
|
|
24451
|
+
if (!addBundleResponse.ok) {
|
|
24452
|
+
logger.error(
|
|
24453
|
+
{
|
|
24454
|
+
category: "bundle_tracking_persist_failed",
|
|
24455
|
+
bundleId: req.bundleId,
|
|
24456
|
+
chainId: batchChainId,
|
|
24457
|
+
txId: batchTx.id,
|
|
24458
|
+
signerName: result.signerName,
|
|
24459
|
+
status: addBundleResponse.status,
|
|
24460
|
+
statusText: addBundleResponse.statusText
|
|
24461
|
+
},
|
|
24462
|
+
"bundle tracking persistence failed for batch request"
|
|
24463
|
+
);
|
|
24464
|
+
bundleTrackingErrors.set(req.id, buildBundleTrackingUnavailableError());
|
|
24465
|
+
}
|
|
24386
24466
|
} catch (error48) {
|
|
24387
24467
|
logger.warn(
|
|
24388
24468
|
{
|
|
24469
|
+
category: "bundle_tracking_persist_failed",
|
|
24389
24470
|
error: getErrorMessage(error48),
|
|
24390
|
-
bundleId: req.bundleId
|
|
24471
|
+
bundleId: req.bundleId,
|
|
24472
|
+
chainId: batchChainId,
|
|
24473
|
+
txId: batchTx.id,
|
|
24474
|
+
signerName: result.signerName
|
|
24391
24475
|
},
|
|
24392
|
-
"
|
|
24476
|
+
"failed to persist bundle tracking for batch request"
|
|
24393
24477
|
);
|
|
24478
|
+
bundleTrackingErrors.set(req.id, buildBundleTrackingUnavailableError());
|
|
24394
24479
|
}
|
|
24395
24480
|
}
|
|
24396
24481
|
}
|
|
@@ -24399,6 +24484,10 @@ async function handleBatchSendPreparedCalls(requests, ctx) {
|
|
|
24399
24484
|
if (validationError) {
|
|
24400
24485
|
return { id: req.id, error: validationError };
|
|
24401
24486
|
}
|
|
24487
|
+
const bundleTrackingError = bundleTrackingErrors.get(req.id);
|
|
24488
|
+
if (bundleTrackingError) {
|
|
24489
|
+
return { id: req.id, error: bundleTrackingError };
|
|
24490
|
+
}
|
|
24402
24491
|
const successResult = parsedRequests.find((r) => r.id === req.id);
|
|
24403
24492
|
if (successResult) {
|
|
24404
24493
|
return { id: req.id, result: { id: successResult.bundleId } };
|
|
@@ -24613,7 +24702,7 @@ __name(checkRpc, "checkRpc");
|
|
|
24613
24702
|
async function checkSignerPool(env, chainId) {
|
|
24614
24703
|
const poolId = env.SIGNER_POOL.idFromName(`pool-${chainId}`);
|
|
24615
24704
|
const pool = env.SIGNER_POOL.get(poolId);
|
|
24616
|
-
const response = await pool.fetch(
|
|
24705
|
+
const response = await pool.fetch(`http://do/status?poolName=pool-${chainId}`);
|
|
24617
24706
|
if (!response.ok) {
|
|
24618
24707
|
throw new Error(`SignerPool status check failed: ${response.status} ${response.statusText}`);
|
|
24619
24708
|
}
|
|
@@ -44831,6 +44920,14 @@ function shouldAttemptReplacementNow(input) {
|
|
|
44831
44920
|
return input.nowMs >= input.lastReplacementAtMs + backoffMs;
|
|
44832
44921
|
}
|
|
44833
44922
|
__name(shouldAttemptReplacementNow, "shouldAttemptReplacementNow");
|
|
44923
|
+
function resolveNonTriggeredReplacement(attempts, maxAttempts) {
|
|
44924
|
+
const nextAttempts = attempts + 1;
|
|
44925
|
+
return {
|
|
44926
|
+
nextAttempts,
|
|
44927
|
+
status: nextAttempts >= maxAttempts ? "stuck" : "pending"
|
|
44928
|
+
};
|
|
44929
|
+
}
|
|
44930
|
+
__name(resolveNonTriggeredReplacement, "resolveNonTriggeredReplacement");
|
|
44834
44931
|
async function withCleanupOnError(operation, cleanup) {
|
|
44835
44932
|
try {
|
|
44836
44933
|
return await operation();
|
|
@@ -44872,6 +44969,25 @@ function isIntentExpired(expiryTimestamp, bufferSeconds = DEFAULT_INTENT_EXPIRY_
|
|
|
44872
44969
|
return currentTime + buffer2 >= expiry;
|
|
44873
44970
|
}
|
|
44874
44971
|
__name(isIntentExpired, "isIntentExpired");
|
|
44972
|
+
function isFillTransactionUnsupportedError(message) {
|
|
44973
|
+
const lower = message.toLowerCase();
|
|
44974
|
+
const mentionsMethod = lower.includes("eth_filltransaction");
|
|
44975
|
+
if (!mentionsMethod) return false;
|
|
44976
|
+
return lower.includes("not available") || lower.includes("does not exist") || lower.includes("method not found");
|
|
44977
|
+
}
|
|
44978
|
+
__name(isFillTransactionUnsupportedError, "isFillTransactionUnsupportedError");
|
|
44979
|
+
function buildRawFallbackBroadcastRequest(input) {
|
|
44980
|
+
return {
|
|
44981
|
+
...input.txParams,
|
|
44982
|
+
nonce: input.nonce,
|
|
44983
|
+
account: input.account,
|
|
44984
|
+
chain: { id: input.chainId },
|
|
44985
|
+
gas: input.gas,
|
|
44986
|
+
maxFeePerGas: input.feeParams.maxFeePerGas,
|
|
44987
|
+
maxPriorityFeePerGas: input.feeParams.maxPriorityFeePerGas
|
|
44988
|
+
};
|
|
44989
|
+
}
|
|
44990
|
+
__name(buildRawFallbackBroadcastRequest, "buildRawFallbackBroadcastRequest");
|
|
44875
44991
|
var SignerDO = class extends DurableObject {
|
|
44876
44992
|
static {
|
|
44877
44993
|
__name(this, "SignerDO");
|
|
@@ -45825,23 +45941,92 @@ var SignerDO = class extends DurableObject {
|
|
|
45825
45941
|
}
|
|
45826
45942
|
}
|
|
45827
45943
|
async signAndBroadcastPrepared(txParams, nonce, chainId, feeParams) {
|
|
45828
|
-
const { walletClient, account } = this.ensureClients(chainId);
|
|
45944
|
+
const { publicClient, walletClient, account } = this.ensureClients(chainId);
|
|
45829
45945
|
try {
|
|
45830
|
-
return await
|
|
45831
|
-
|
|
45946
|
+
return await this.sendWithPrimaryPath(
|
|
45947
|
+
txParams,
|
|
45832
45948
|
nonce,
|
|
45833
|
-
|
|
45834
|
-
|
|
45835
|
-
|
|
45836
|
-
|
|
45837
|
-
|
|
45949
|
+
chainId,
|
|
45950
|
+
feeParams,
|
|
45951
|
+
walletClient,
|
|
45952
|
+
account
|
|
45953
|
+
);
|
|
45838
45954
|
} catch (error48) {
|
|
45839
|
-
const
|
|
45955
|
+
const primaryMessage = getErrorMessage(error48);
|
|
45956
|
+
if (!isFillTransactionUnsupportedError(primaryMessage)) {
|
|
45957
|
+
throw new SignerDOError(
|
|
45958
|
+
`Failed to broadcast transaction: ${primaryMessage}`,
|
|
45959
|
+
"BROADCAST_FAILED"
|
|
45960
|
+
);
|
|
45961
|
+
}
|
|
45962
|
+
console.warn("[SignerDO] broadcast fallback activated", {
|
|
45963
|
+
reason: "eth_filltransaction_unsupported",
|
|
45964
|
+
chainId
|
|
45965
|
+
});
|
|
45966
|
+
try {
|
|
45967
|
+
const txHash = await this.sendWithRawFallback(
|
|
45968
|
+
txParams,
|
|
45969
|
+
nonce,
|
|
45970
|
+
chainId,
|
|
45971
|
+
feeParams,
|
|
45972
|
+
publicClient,
|
|
45973
|
+
walletClient,
|
|
45974
|
+
account
|
|
45975
|
+
);
|
|
45976
|
+
console.info("[SignerDO] broadcast fallback success", { chainId, txHash });
|
|
45977
|
+
return txHash;
|
|
45978
|
+
} catch (fallbackError) {
|
|
45979
|
+
const fallbackMessage = getErrorMessage(fallbackError);
|
|
45980
|
+
const message = `${primaryMessage}; fallback failed: ${fallbackMessage}`;
|
|
45981
|
+
console.error("[SignerDO] broadcast fallback failed", {
|
|
45982
|
+
chainId,
|
|
45983
|
+
primaryError: primaryMessage,
|
|
45984
|
+
fallbackError: fallbackMessage
|
|
45985
|
+
});
|
|
45986
|
+
throw new SignerDOError(
|
|
45987
|
+
`Failed to broadcast transaction: ${message}`,
|
|
45988
|
+
"BROADCAST_FAILED"
|
|
45989
|
+
);
|
|
45990
|
+
}
|
|
45991
|
+
}
|
|
45992
|
+
}
|
|
45993
|
+
async sendWithPrimaryPath(txParams, nonce, chainId, feeParams, walletClient, account) {
|
|
45994
|
+
return walletClient.sendTransaction({
|
|
45995
|
+
...txParams,
|
|
45996
|
+
nonce,
|
|
45997
|
+
account,
|
|
45998
|
+
chain: { id: chainId },
|
|
45999
|
+
maxFeePerGas: feeParams.maxFeePerGas,
|
|
46000
|
+
maxPriorityFeePerGas: feeParams.maxPriorityFeePerGas
|
|
46001
|
+
});
|
|
46002
|
+
}
|
|
46003
|
+
async sendWithRawFallback(txParams, nonce, chainId, feeParams, publicClient, walletClient, account) {
|
|
46004
|
+
const gas = await publicClient.estimateGas({
|
|
46005
|
+
account: account.address,
|
|
46006
|
+
to: txParams.to,
|
|
46007
|
+
data: txParams.data,
|
|
46008
|
+
value: txParams.value,
|
|
46009
|
+
nonce,
|
|
46010
|
+
maxFeePerGas: feeParams.maxFeePerGas,
|
|
46011
|
+
maxPriorityFeePerGas: feeParams.maxPriorityFeePerGas,
|
|
46012
|
+
authorizationList: txParams.authorizationList
|
|
46013
|
+
});
|
|
46014
|
+
const request = buildRawFallbackBroadcastRequest({
|
|
46015
|
+
txParams,
|
|
46016
|
+
nonce,
|
|
46017
|
+
chainId,
|
|
46018
|
+
account,
|
|
46019
|
+
gas,
|
|
46020
|
+
feeParams
|
|
46021
|
+
});
|
|
46022
|
+
const serializedTransaction = await walletClient.signTransaction(request);
|
|
46023
|
+
if (!serializedTransaction) {
|
|
45840
46024
|
throw new SignerDOError(
|
|
45841
|
-
|
|
46025
|
+
"Fallback signing returned empty serialized transaction",
|
|
45842
46026
|
"BROADCAST_FAILED"
|
|
45843
46027
|
);
|
|
45844
46028
|
}
|
|
46029
|
+
return publicClient.sendRawTransaction({ serializedTransaction });
|
|
45845
46030
|
}
|
|
45846
46031
|
/**
|
|
45847
46032
|
* Encode an intent to bytes for the Orchestrator.execute() call
|
|
@@ -46007,9 +46192,18 @@ var SignerDO = class extends DurableObject {
|
|
|
46007
46192
|
staleThresholdPerGas: config2.triggerThresholdWei
|
|
46008
46193
|
});
|
|
46009
46194
|
if (!triggerReplacement) {
|
|
46195
|
+
const resolution = resolveNonTriggeredReplacement(attempts, config2.maxAttempts);
|
|
46010
46196
|
this.sql.exec(
|
|
46011
|
-
|
|
46012
|
-
|
|
46197
|
+
`
|
|
46198
|
+
UPDATE pending_transactions
|
|
46199
|
+
SET status = ?,
|
|
46200
|
+
replacement_attempts = ?,
|
|
46201
|
+
last_replacement_at = ?
|
|
46202
|
+
WHERE id = ?
|
|
46203
|
+
`,
|
|
46204
|
+
resolution.status,
|
|
46205
|
+
resolution.nextAttempts,
|
|
46206
|
+
nowMs,
|
|
46013
46207
|
txId
|
|
46014
46208
|
);
|
|
46015
46209
|
return "skipped";
|
|
@@ -46522,6 +46716,7 @@ var SignerPoolDO = class extends DurableObject2 {
|
|
|
46522
46716
|
|
|
46523
46717
|
// src/durable-objects/bundle-status.do.ts
|
|
46524
46718
|
import { DurableObject as DurableObject3 } from "cloudflare:workers";
|
|
46719
|
+
var DEFAULT_BUNDLE_UNRESOLVED_SLA_MS = 3e5;
|
|
46525
46720
|
var BundleStatusDO = class extends DurableObject3 {
|
|
46526
46721
|
static {
|
|
46527
46722
|
__name(this, "BundleStatusDO");
|
|
@@ -46546,6 +46741,7 @@ var BundleStatusDO = class extends DurableObject3 {
|
|
|
46546
46741
|
CREATE INDEX IF NOT EXISTS idx_bundle_transactions_tx_id ON bundle_transactions(tx_id);
|
|
46547
46742
|
CREATE INDEX IF NOT EXISTS idx_bundle_transactions_signer ON bundle_transactions(signer_name);
|
|
46548
46743
|
`);
|
|
46744
|
+
this.ensureBundleTransactionsSchema();
|
|
46549
46745
|
this.sql.exec(`
|
|
46550
46746
|
CREATE TABLE IF NOT EXISTS pending_bundles (
|
|
46551
46747
|
bundle_id TEXT PRIMARY KEY,
|
|
@@ -46664,6 +46860,73 @@ var BundleStatusDO = class extends DurableObject3 {
|
|
|
46664
46860
|
}
|
|
46665
46861
|
}
|
|
46666
46862
|
}
|
|
46863
|
+
/**
|
|
46864
|
+
* Backward-compatible migration for bundle_transactions metadata.
|
|
46865
|
+
* Adds created_at for unresolved-bundle SLA tracking.
|
|
46866
|
+
*/
|
|
46867
|
+
ensureBundleTransactionsSchema() {
|
|
46868
|
+
const columns = new Set(
|
|
46869
|
+
this.sql.exec("PRAGMA table_info(bundle_transactions)").toArray().map((row) => String(row.name ?? ""))
|
|
46870
|
+
);
|
|
46871
|
+
if (!columns.has("created_at")) {
|
|
46872
|
+
this.sql.exec(
|
|
46873
|
+
"ALTER TABLE bundle_transactions ADD COLUMN created_at INTEGER NOT NULL DEFAULT 0"
|
|
46874
|
+
);
|
|
46875
|
+
}
|
|
46876
|
+
const now = Date.now();
|
|
46877
|
+
this.sql.exec(
|
|
46878
|
+
"UPDATE bundle_transactions SET created_at = ? WHERE created_at IS NULL OR created_at <= 0",
|
|
46879
|
+
now
|
|
46880
|
+
);
|
|
46881
|
+
}
|
|
46882
|
+
getChainIdFromName() {
|
|
46883
|
+
const name = this.ctx.id.name;
|
|
46884
|
+
if (!name) return 0;
|
|
46885
|
+
const parts = name.split("-");
|
|
46886
|
+
if (parts.length !== 3 || parts[0] !== "bundle" || parts[1] !== "status") return 0;
|
|
46887
|
+
const chainId = Number.parseInt(parts[2], 10);
|
|
46888
|
+
return Number.isFinite(chainId) ? chainId : 0;
|
|
46889
|
+
}
|
|
46890
|
+
getBundleUnresolvedSlaMs() {
|
|
46891
|
+
const parsed = Number.parseInt(
|
|
46892
|
+
this.env.BUNDLE_UNRESOLVED_SLA_MS ?? String(DEFAULT_BUNDLE_UNRESOLVED_SLA_MS),
|
|
46893
|
+
10
|
|
46894
|
+
);
|
|
46895
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_BUNDLE_UNRESOLVED_SLA_MS;
|
|
46896
|
+
}
|
|
46897
|
+
async fetchTxStatusFromSigner(txId, signerName) {
|
|
46898
|
+
try {
|
|
46899
|
+
const signerId = this.env.SIGNER.idFromName(signerName);
|
|
46900
|
+
const signer = this.env.SIGNER.get(signerId);
|
|
46901
|
+
const response = await signer.fetch(`http://do/get_tx_status?txId=${txId}`);
|
|
46902
|
+
if (!response.ok) return null;
|
|
46903
|
+
return await response.json();
|
|
46904
|
+
} catch (error48) {
|
|
46905
|
+
logger.warn(
|
|
46906
|
+
{
|
|
46907
|
+
event: "bundle_status_signer_fetch_failed",
|
|
46908
|
+
txId,
|
|
46909
|
+
signerName,
|
|
46910
|
+
error: getErrorMessage(error48)
|
|
46911
|
+
},
|
|
46912
|
+
"failed to fetch transaction status from signer"
|
|
46913
|
+
);
|
|
46914
|
+
return null;
|
|
46915
|
+
}
|
|
46916
|
+
}
|
|
46917
|
+
async probeSignerForTxStatus(txId, chainId, skipSignerName) {
|
|
46918
|
+
const signerCount = Number.parseInt(this.env.RELAYER_COUNT ?? "1", 10);
|
|
46919
|
+
const maxSigners = Number.isFinite(signerCount) && signerCount > 0 ? signerCount : 1;
|
|
46920
|
+
for (let index2 = 0; index2 < maxSigners; index2 += 1) {
|
|
46921
|
+
const signerName = `signer-${chainId}-${index2}`;
|
|
46922
|
+
if (skipSignerName && signerName === skipSignerName) continue;
|
|
46923
|
+
const txStatus = await this.fetchTxStatusFromSigner(txId, signerName);
|
|
46924
|
+
if (txStatus) {
|
|
46925
|
+
return { txStatus, signerName };
|
|
46926
|
+
}
|
|
46927
|
+
}
|
|
46928
|
+
return { txStatus: null, signerName: null };
|
|
46929
|
+
}
|
|
46667
46930
|
/**
|
|
46668
46931
|
* HTTP handler for BundleStatusDO endpoints
|
|
46669
46932
|
*/
|
|
@@ -46716,10 +46979,11 @@ var BundleStatusDO = class extends DurableObject3 {
|
|
|
46716
46979
|
*/
|
|
46717
46980
|
async add_bundle_tx(bundleId, txId, signerName) {
|
|
46718
46981
|
this.sql.exec(
|
|
46719
|
-
"INSERT OR IGNORE INTO bundle_transactions (bundle_id, tx_id, signer_name) VALUES (?, ?, ?)",
|
|
46982
|
+
"INSERT OR IGNORE INTO bundle_transactions (bundle_id, tx_id, signer_name, created_at) VALUES (?, ?, ?, ?)",
|
|
46720
46983
|
bundleId,
|
|
46721
46984
|
txId,
|
|
46722
|
-
signerName ?? null
|
|
46985
|
+
signerName ?? null,
|
|
46986
|
+
Date.now()
|
|
46723
46987
|
);
|
|
46724
46988
|
}
|
|
46725
46989
|
/**
|
|
@@ -46727,7 +46991,7 @@ var BundleStatusDO = class extends DurableObject3 {
|
|
|
46727
46991
|
*/
|
|
46728
46992
|
async get_bundle_status(bundleId) {
|
|
46729
46993
|
const txRows = this.sql.exec(
|
|
46730
|
-
"SELECT tx_id, signer_name FROM bundle_transactions WHERE bundle_id = ?",
|
|
46994
|
+
"SELECT tx_id, signer_name, created_at FROM bundle_transactions WHERE bundle_id = ?",
|
|
46731
46995
|
bundleId
|
|
46732
46996
|
).toArray();
|
|
46733
46997
|
if (txRows.length === 0) {
|
|
@@ -46749,26 +47013,113 @@ var BundleStatusDO = class extends DurableObject3 {
|
|
|
46749
47013
|
};
|
|
46750
47014
|
}
|
|
46751
47015
|
const transactions = [];
|
|
47016
|
+
const chainId = this.getChainIdFromName();
|
|
46752
47017
|
for (const row of txRows) {
|
|
46753
47018
|
const txId = row.tx_id;
|
|
46754
47019
|
const signerName = row.signer_name;
|
|
46755
47020
|
if (!signerName) {
|
|
46756
|
-
|
|
47021
|
+
logger.warn(
|
|
47022
|
+
{
|
|
47023
|
+
event: "bundle_status_missing_signer_name",
|
|
47024
|
+
bundleId,
|
|
47025
|
+
txId
|
|
47026
|
+
},
|
|
47027
|
+
"bundle transaction missing signer_name; probing signers"
|
|
47028
|
+
);
|
|
46757
47029
|
}
|
|
46758
47030
|
try {
|
|
46759
|
-
|
|
46760
|
-
|
|
46761
|
-
|
|
46762
|
-
|
|
47031
|
+
let txStatus = signerName ? await this.fetchTxStatusFromSigner(txId, signerName) : null;
|
|
47032
|
+
let resolvedSignerName = signerName;
|
|
47033
|
+
if (!txStatus && chainId > 0) {
|
|
47034
|
+
const probed = await this.probeSignerForTxStatus(
|
|
47035
|
+
txId,
|
|
47036
|
+
chainId,
|
|
47037
|
+
signerName ?? void 0
|
|
47038
|
+
);
|
|
47039
|
+
txStatus = probed.txStatus;
|
|
47040
|
+
resolvedSignerName = probed.signerName;
|
|
47041
|
+
}
|
|
47042
|
+
if (!txStatus) {
|
|
47043
|
+
logger.warn(
|
|
47044
|
+
{
|
|
47045
|
+
event: "bundle_status_signer_probe_failed",
|
|
47046
|
+
bundleId,
|
|
47047
|
+
txId,
|
|
47048
|
+
signerName,
|
|
47049
|
+
chainId
|
|
47050
|
+
},
|
|
47051
|
+
"failed to resolve signer tx status for bundle transaction"
|
|
47052
|
+
);
|
|
46763
47053
|
continue;
|
|
46764
47054
|
}
|
|
46765
|
-
|
|
47055
|
+
if (resolvedSignerName && resolvedSignerName !== signerName) {
|
|
47056
|
+
try {
|
|
47057
|
+
this.sql.exec(
|
|
47058
|
+
"UPDATE bundle_transactions SET signer_name = ? WHERE bundle_id = ? AND tx_id = ?",
|
|
47059
|
+
resolvedSignerName,
|
|
47060
|
+
bundleId,
|
|
47061
|
+
txId
|
|
47062
|
+
);
|
|
47063
|
+
} catch (error48) {
|
|
47064
|
+
logger.warn(
|
|
47065
|
+
{
|
|
47066
|
+
event: "bundle_status_signer_cache_update_failed",
|
|
47067
|
+
bundleId,
|
|
47068
|
+
txId,
|
|
47069
|
+
signerName,
|
|
47070
|
+
resolvedSignerName,
|
|
47071
|
+
chainId,
|
|
47072
|
+
error: getErrorMessage(error48)
|
|
47073
|
+
},
|
|
47074
|
+
"failed to cache resolved signer name for bundle transaction"
|
|
47075
|
+
);
|
|
47076
|
+
}
|
|
47077
|
+
}
|
|
46766
47078
|
transactions.push(txStatus);
|
|
46767
|
-
} catch {
|
|
47079
|
+
} catch (error48) {
|
|
47080
|
+
logger.warn(
|
|
47081
|
+
{
|
|
47082
|
+
event: "bundle_status_tx_resolution_failed",
|
|
47083
|
+
bundleId,
|
|
47084
|
+
txId,
|
|
47085
|
+
signerName,
|
|
47086
|
+
chainId,
|
|
47087
|
+
error: getErrorMessage(error48)
|
|
47088
|
+
},
|
|
47089
|
+
"failed during bundle transaction status resolution"
|
|
47090
|
+
);
|
|
46768
47091
|
continue;
|
|
46769
47092
|
}
|
|
46770
47093
|
}
|
|
46771
47094
|
if (transactions.length === 0) {
|
|
47095
|
+
const oldestCreatedAt = txRows.reduce((oldest, row) => {
|
|
47096
|
+
const createdAt = Number(row.created_at ?? 0);
|
|
47097
|
+
if (!Number.isFinite(createdAt) || createdAt <= 0) return oldest;
|
|
47098
|
+
return Math.min(oldest, createdAt);
|
|
47099
|
+
}, Number.MAX_SAFE_INTEGER);
|
|
47100
|
+
const safeOldestCreatedAt = oldestCreatedAt === Number.MAX_SAFE_INTEGER ? Date.now() : oldestCreatedAt;
|
|
47101
|
+
const ageMs = Date.now() - safeOldestCreatedAt;
|
|
47102
|
+
const unresolvedSlaMs = this.getBundleUnresolvedSlaMs();
|
|
47103
|
+
if (ageMs >= unresolvedSlaMs) {
|
|
47104
|
+
logger.error(
|
|
47105
|
+
{
|
|
47106
|
+
event: "bundle_unresolvable_sla_failed",
|
|
47107
|
+
reason: "unresolvable_bundle_tracking_timeout",
|
|
47108
|
+
bundleId,
|
|
47109
|
+
ageMs,
|
|
47110
|
+
unresolvedSlaMs,
|
|
47111
|
+
txCount: txRows.length,
|
|
47112
|
+
chainId
|
|
47113
|
+
},
|
|
47114
|
+
"bundle unresolved beyond SLA; terminalizing as failed"
|
|
47115
|
+
);
|
|
47116
|
+
return {
|
|
47117
|
+
bundleId,
|
|
47118
|
+
status: "failed",
|
|
47119
|
+
statusCode: 300,
|
|
47120
|
+
receipts: []
|
|
47121
|
+
};
|
|
47122
|
+
}
|
|
46772
47123
|
return {
|
|
46773
47124
|
bundleId,
|
|
46774
47125
|
status: "pending",
|
|
@@ -47478,6 +47829,7 @@ var HttpAuthNonceDO = class extends DurableObject5 {
|
|
|
47478
47829
|
};
|
|
47479
47830
|
|
|
47480
47831
|
// src/index.ts
|
|
47832
|
+
var MAX_MONITOR_ATTEMPTS = 30;
|
|
47481
47833
|
var app = new Hono2();
|
|
47482
47834
|
app.use("*", async (c2, next) => {
|
|
47483
47835
|
const allowedOrigins = c2.env.CORS_ALLOWED_ORIGINS;
|
|
@@ -47562,24 +47914,70 @@ async function handleQueue(batch, env) {
|
|
|
47562
47914
|
for (const msg of batch.messages) {
|
|
47563
47915
|
const job = msg.body;
|
|
47564
47916
|
try {
|
|
47565
|
-
const jobType =
|
|
47917
|
+
const jobType = resolveQueueJobType(job);
|
|
47566
47918
|
switch (jobType) {
|
|
47567
47919
|
case "monitor":
|
|
47568
47920
|
await handleMonitorJob(msg, env);
|
|
47569
47921
|
break;
|
|
47570
47922
|
default:
|
|
47571
|
-
logger.warn({ job }, "
|
|
47923
|
+
logger.warn({ job, attempts: msg.attempts }, "queue invalid job payload");
|
|
47572
47924
|
msg.ack();
|
|
47573
47925
|
}
|
|
47574
47926
|
} catch (error48) {
|
|
47575
|
-
logger.error(
|
|
47927
|
+
logger.error(
|
|
47928
|
+
{ job, attempts: msg.attempts, error: getErrorMessage(error48) },
|
|
47929
|
+
"queue processing error"
|
|
47930
|
+
);
|
|
47576
47931
|
msg.retry({ delaySeconds: 30 });
|
|
47577
47932
|
}
|
|
47578
47933
|
}
|
|
47579
47934
|
}
|
|
47580
47935
|
__name(handleQueue, "handleQueue");
|
|
47936
|
+
function resolveQueueJobType(job) {
|
|
47937
|
+
if (!job || typeof job !== "object") return "unknown";
|
|
47938
|
+
if (!("type" in job)) return "monitor";
|
|
47939
|
+
return job.type === "monitor" ? "monitor" : "unknown";
|
|
47940
|
+
}
|
|
47941
|
+
__name(resolveQueueJobType, "resolveQueueJobType");
|
|
47942
|
+
function getMonitorAttempt(msg) {
|
|
47943
|
+
return msg.attempts ?? msg.body.attempt ?? 0;
|
|
47944
|
+
}
|
|
47945
|
+
__name(getMonitorAttempt, "getMonitorAttempt");
|
|
47946
|
+
async function notifySignerFinalization(env, signerName, txId, txHash, status) {
|
|
47947
|
+
try {
|
|
47948
|
+
const signerId = env.SIGNER.idFromName(signerName);
|
|
47949
|
+
const signer = env.SIGNER.get(signerId);
|
|
47950
|
+
const response = await signer.fetch(`http://do/finalized?signerName=${signerName}`, {
|
|
47951
|
+
method: "POST",
|
|
47952
|
+
headers: { "Content-Type": "application/json" },
|
|
47953
|
+
body: JSON.stringify({ txId, txHash, status })
|
|
47954
|
+
});
|
|
47955
|
+
return response.ok;
|
|
47956
|
+
} catch {
|
|
47957
|
+
return false;
|
|
47958
|
+
}
|
|
47959
|
+
}
|
|
47960
|
+
__name(notifySignerFinalization, "notifySignerFinalization");
|
|
47961
|
+
async function finalizeMonitorAsFailed(msg, env, reason) {
|
|
47962
|
+
const { txId, txHash, signerName } = msg.body;
|
|
47963
|
+
const finalized = await notifySignerFinalization(env, signerName, txId, txHash, "failed");
|
|
47964
|
+
if (finalized) {
|
|
47965
|
+
logger.error(
|
|
47966
|
+
{ txId, txHash, signerName, attempts: getMonitorAttempt(msg), reason },
|
|
47967
|
+
"monitor job finalized as failed"
|
|
47968
|
+
);
|
|
47969
|
+
return true;
|
|
47970
|
+
}
|
|
47971
|
+
logger.error(
|
|
47972
|
+
{ txId, txHash, signerName, attempts: getMonitorAttempt(msg), reason },
|
|
47973
|
+
"failed to finalize monitor job as failed"
|
|
47974
|
+
);
|
|
47975
|
+
return false;
|
|
47976
|
+
}
|
|
47977
|
+
__name(finalizeMonitorAsFailed, "finalizeMonitorAsFailed");
|
|
47581
47978
|
async function handleMonitorJob(msg, env) {
|
|
47582
|
-
const { txId, txHash, signerName
|
|
47979
|
+
const { txId, txHash, signerName } = msg.body;
|
|
47980
|
+
const attempt = getMonitorAttempt(msg);
|
|
47583
47981
|
const fallbackChainIds = getChainIds(env);
|
|
47584
47982
|
const chainId = msg.body.chainId ?? (fallbackChainIds.length === 1 ? fallbackChainIds[0] : void 0);
|
|
47585
47983
|
if (!chainId) {
|
|
@@ -47590,24 +47988,33 @@ async function handleMonitorJob(msg, env) {
|
|
|
47590
47988
|
const receipt = await getTransactionReceipt2(getChainRpcUrl(chainId, env), txHash);
|
|
47591
47989
|
if (receipt) {
|
|
47592
47990
|
await logBundleGasTelemetry(env, txId, chainId, txHash, receipt.gasUsed);
|
|
47593
|
-
const
|
|
47594
|
-
const
|
|
47595
|
-
|
|
47596
|
-
|
|
47597
|
-
|
|
47598
|
-
|
|
47599
|
-
|
|
47600
|
-
|
|
47601
|
-
|
|
47602
|
-
|
|
47603
|
-
});
|
|
47991
|
+
const finalStatus = receipt.status === "0x1" ? "confirmed" : "failed";
|
|
47992
|
+
const finalized = await notifySignerFinalization(env, signerName, txId, txHash, finalStatus);
|
|
47993
|
+
if (!finalized) {
|
|
47994
|
+
msg.retry({ delaySeconds: 30 });
|
|
47995
|
+
logger.error(
|
|
47996
|
+
{ txId, txHash, signerName, attempt },
|
|
47997
|
+
"failed to notify signer finalization, retrying"
|
|
47998
|
+
);
|
|
47999
|
+
return;
|
|
48000
|
+
}
|
|
47604
48001
|
msg.ack();
|
|
47605
|
-
logger.info(
|
|
47606
|
-
{ txId, txHash, status: receipt.status === "0x1" ? "confirmed" : "failed" },
|
|
47607
|
-
"transaction finalized"
|
|
47608
|
-
);
|
|
48002
|
+
logger.info({ txId, txHash, status: finalStatus }, "transaction finalized");
|
|
47609
48003
|
} else {
|
|
47610
|
-
|
|
48004
|
+
if (attempt >= MAX_MONITOR_ATTEMPTS) {
|
|
48005
|
+
const finalized = await finalizeMonitorAsFailed(
|
|
48006
|
+
msg,
|
|
48007
|
+
env,
|
|
48008
|
+
"monitor retries exhausted without receipt"
|
|
48009
|
+
);
|
|
48010
|
+
if (finalized) {
|
|
48011
|
+
msg.ack();
|
|
48012
|
+
} else {
|
|
48013
|
+
msg.retry({ delaySeconds: 30 });
|
|
48014
|
+
}
|
|
48015
|
+
return;
|
|
48016
|
+
}
|
|
48017
|
+
const delaySeconds = Math.min(Math.pow(2, attempt), 60);
|
|
47611
48018
|
msg.retry({ delaySeconds });
|
|
47612
48019
|
logger.debug({ txId, txHash, attempt, delaySeconds }, "transaction pending, retrying");
|
|
47613
48020
|
}
|