@msgboard/relayer 0.0.31 → 0.0.32
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/README.md +1 -1
- package/dist/actions/repricing.d.ts +41 -0
- package/dist/actions/repricing.d.ts.map +1 -0
- package/dist/actions/repricing.js +43 -0
- package/dist/actions/repricing.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/stores/pending-tx.d.ts +46 -0
- package/dist/stores/pending-tx.d.ts.map +1 -0
- package/dist/stores/pending-tx.js +47 -0
- package/dist/stores/pending-tx.js.map +1 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ await archive.migrate()
|
|
|
59
59
|
const recent = await archive.query({ chainId: 943, category: 'lorem', limit: 20 })
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
`query()` filters by `chainId`, `category` (hex or decoded text), `since`/`until`, `contains` (substring match on decoded
|
|
62
|
+
`query()` filters by `chainId`, `category` (hex or decoded text), `since`/`until`, `contains` (substring match on the decoded data text), `limit`, and `offset`.
|
|
63
63
|
|
|
64
64
|
The storage and query layer lives in [`@msgboard/history`](../history); `postgresArchiveSink` is the adapter that drives it from the relayer heartbeat. Use `@msgboard/history`'s `archiveServer` to expose the same archive over an HTTP query API.
|
|
65
65
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { RelayerAction, RelayerContext } from '../types.js';
|
|
2
|
+
import type { PendingTxTracker, TxFees } from '../stores/pending-tx.js';
|
|
3
|
+
/** What the caller's submit fn is handed: the nonce + fees to use, the item, and the runtime ctx. */
|
|
4
|
+
export type SubmitRequest<T> = {
|
|
5
|
+
item: T;
|
|
6
|
+
nonce: number;
|
|
7
|
+
fees: TxFees;
|
|
8
|
+
context: RelayerContext;
|
|
9
|
+
/** True when this is a replace-by-fee resubmission of an already-pending nonce. */
|
|
10
|
+
replacement: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type RepricingActionOptions<T> = {
|
|
13
|
+
/** Tracks in-flight txs by nonce (window + RBF state). */
|
|
14
|
+
tracker: PendingTxTracker;
|
|
15
|
+
/** Pure description for observe-mode logging. */
|
|
16
|
+
describe: (item: T, context: RelayerContext) => string;
|
|
17
|
+
/** Build + send ONE tx at the given nonce/fees. Returns the tx hash. */
|
|
18
|
+
submit: (req: SubmitRequest<T>) => Promise<{
|
|
19
|
+
hash: string;
|
|
20
|
+
}>;
|
|
21
|
+
/** Initial EIP-1559 fees for a fresh settlement (e.g. read from the chain). */
|
|
22
|
+
initialFees: (item: T, context: RelayerContext) => Promise<TxFees>;
|
|
23
|
+
/** A pending tx older than this is replaced-by-fee. */
|
|
24
|
+
staleMs: number;
|
|
25
|
+
/** Stable per-item key so a re-tick of the same settlement reuses its nonce. Defaults to JSON. */
|
|
26
|
+
itemKey?: (item: T) => string;
|
|
27
|
+
/** Injectable clock (tests). */
|
|
28
|
+
now?: () => number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Wraps a single-tx submit fn with a nonce window (pipeline multiple settlements)
|
|
32
|
+
* and replace-by-fee (bump a stuck tx). Generic — the relayer spec §13 deferred
|
|
33
|
+
* "nonce-window / repricing Action wrapper". Knows nothing about games.
|
|
34
|
+
*
|
|
35
|
+
* Safety: `describe` is pure (observe mode never submits). A submitted nonce is
|
|
36
|
+
* remembered; a re-tick of the same item before it mines RBFs the SAME nonce
|
|
37
|
+
* (never a second tx, never a forged state — it only re-sends the same calldata
|
|
38
|
+
* at a higher fee). When the window is full a new item is a no-op this tick.
|
|
39
|
+
*/
|
|
40
|
+
export declare const repricingAction: <T>(options: RepricingActionOptions<T>) => RelayerAction<T>;
|
|
41
|
+
//# sourceMappingURL=repricing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repricing.d.ts","sourceRoot":"","sources":["../../src/actions/repricing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEvE,qGAAqG;AACrG,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,IAAI,EAAE,CAAC,CAAA;IACP,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,cAAc,CAAA;IACvB,mFAAmF;IACnF,WAAW,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,sBAAsB,CAAC,CAAC,IAAI;IACtC,0DAA0D;IAC1D,OAAO,EAAE,gBAAgB,CAAA;IACzB,iDAAiD;IACjD,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,KAAK,MAAM,CAAA;IACtD,wEAAwE;IACxE,MAAM,EAAE,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC5D,+EAA+E;IAC/E,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAClE,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAA;IACf,kGAAkG;IAClG,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAA;IAC7B,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,SAAS,sBAAsB,CAAC,CAAC,CAAC,KAAG,aAAa,CAAC,CAAC,CAkCtF,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a single-tx submit fn with a nonce window (pipeline multiple settlements)
|
|
3
|
+
* and replace-by-fee (bump a stuck tx). Generic — the relayer spec §13 deferred
|
|
4
|
+
* "nonce-window / repricing Action wrapper". Knows nothing about games.
|
|
5
|
+
*
|
|
6
|
+
* Safety: `describe` is pure (observe mode never submits). A submitted nonce is
|
|
7
|
+
* remembered; a re-tick of the same item before it mines RBFs the SAME nonce
|
|
8
|
+
* (never a second tx, never a forged state — it only re-sends the same calldata
|
|
9
|
+
* at a higher fee). When the window is full a new item is a no-op this tick.
|
|
10
|
+
*/
|
|
11
|
+
export const repricingAction = (options) => {
|
|
12
|
+
const key = options.itemKey ?? ((item) => JSON.stringify(item));
|
|
13
|
+
// item-key -> the nonce we assigned it, so a re-tick reuses it for RBF
|
|
14
|
+
const nonceOf = new Map();
|
|
15
|
+
return {
|
|
16
|
+
describe: (item, context) => options.describe(item, context),
|
|
17
|
+
execute: async (item, context) => {
|
|
18
|
+
const k = key(item);
|
|
19
|
+
const existing = nonceOf.get(k);
|
|
20
|
+
// Already in flight: replace-by-fee iff stale, else leave it.
|
|
21
|
+
if (existing !== undefined) {
|
|
22
|
+
if (!options.tracker.isStale(existing, options.staleMs)) {
|
|
23
|
+
return { ok: true, ref: `nonce:${existing}`, meta: { skipped: 'still-pending' } };
|
|
24
|
+
}
|
|
25
|
+
const fees = options.tracker.bumpFees(existing);
|
|
26
|
+
const { hash } = await options.submit({ item, nonce: existing, fees, context, replacement: true });
|
|
27
|
+
options.tracker.recordSubmission(existing, { hash, ...fees });
|
|
28
|
+
return { ok: true, ref: hash, meta: { replacement: true, nonce: existing } };
|
|
29
|
+
}
|
|
30
|
+
// New settlement: claim a nonce from the window.
|
|
31
|
+
const nonce = options.tracker.claim();
|
|
32
|
+
if (nonce === undefined) {
|
|
33
|
+
return { ok: false, meta: { deferred: 'nonce-window-full' } };
|
|
34
|
+
}
|
|
35
|
+
const fees = await options.initialFees(item, context);
|
|
36
|
+
const { hash } = await options.submit({ item, nonce, fees, context, replacement: false });
|
|
37
|
+
nonceOf.set(k, nonce);
|
|
38
|
+
options.tracker.recordSubmission(nonce, { hash, ...fees });
|
|
39
|
+
return { ok: true, ref: hash, meta: { nonce } };
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=repricing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repricing.js","sourceRoot":"","sources":["../../src/actions/repricing.ts"],"names":[],"mappings":"AA8BA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAI,OAAkC,EAAoB,EAAE;IACzF,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC,IAAO,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;IAClE,uEAAuE;IACvE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA;IAEzC,OAAO;QACL,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;QAC5D,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YAC/B,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAA;YACnB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YAE/B,8DAA8D;YAC9D,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;oBACxD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE,CAAA;gBACnF,CAAC;gBACD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;gBAC/C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;gBAClG,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;gBAC7D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAA;YAC9E,CAAC;YAED,iDAAiD;YACjD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;YACrC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,mBAAmB,EAAE,EAAE,CAAA;YAC/D,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;YACrD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAA;YACzF,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;YACrB,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;YAC1D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,CAAA;QACjD,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export { forwardMessageAction } from './actions/forward-message.js';
|
|
|
18
18
|
export { sendValueAction } from './actions/send-value.js';
|
|
19
19
|
export { webhookAction } from './actions/webhook.js';
|
|
20
20
|
export { noopAction } from './actions/noop.js';
|
|
21
|
+
export { repricingAction } from './actions/repricing.js';
|
|
22
|
+
export type { RepricingActionOptions, SubmitRequest } from './actions/repricing.js';
|
|
23
|
+
export { createPendingTxTracker } from './stores/pending-tx.js';
|
|
24
|
+
export type { PendingTxTracker, PendingTx, PendingTxTrackerOptions, TxFees } from './stores/pending-tx.js';
|
|
21
25
|
export { httpQueueSource } from './sources/http-queue.js';
|
|
22
26
|
export type { HttpQueueSource, HttpQueueSourceOptions } from './sources/http-queue.js';
|
|
23
27
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,YAAY,EACV,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,aAAa,EACb,YAAY,EACZ,UAAU,GACX,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAErD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAClG,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,YAAY,EACV,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,aAAa,EACb,YAAY,EACZ,UAAU,GACX,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAErD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAClG,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,YAAY,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACnF,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAE1G,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -14,5 +14,7 @@ export { forwardMessageAction } from './actions/forward-message.js';
|
|
|
14
14
|
export { sendValueAction } from './actions/send-value.js';
|
|
15
15
|
export { webhookAction } from './actions/webhook.js';
|
|
16
16
|
export { noopAction } from './actions/noop.js';
|
|
17
|
+
export { repricingAction } from './actions/repricing.js';
|
|
18
|
+
export { createPendingTxTracker } from './stores/pending-tx.js';
|
|
17
19
|
export { httpQueueSource } from './sources/http-queue.js';
|
|
18
20
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAgB1C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAEjE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAgB1C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAEjE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAG/D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Fee fields for an EIP-1559 settle tx, in wei. */
|
|
2
|
+
export type TxFees = {
|
|
3
|
+
maxFeePerGas: bigint;
|
|
4
|
+
maxPriorityFeePerGas: bigint;
|
|
5
|
+
};
|
|
6
|
+
/** What we retain about one in-flight tx, keyed by its nonce. */
|
|
7
|
+
export type PendingTx = {
|
|
8
|
+
nonce: number;
|
|
9
|
+
hash: string;
|
|
10
|
+
fees: TxFees;
|
|
11
|
+
submittedAt: number;
|
|
12
|
+
};
|
|
13
|
+
export type PendingTxTrackerOptions = {
|
|
14
|
+
/** Max number of nonces in flight at once (the pipeline depth). */
|
|
15
|
+
windowSize: number;
|
|
16
|
+
/** First nonce this worker owns (from `getTransactionCount(account, 'pending')`). */
|
|
17
|
+
baseNonce: number;
|
|
18
|
+
/** Injectable clock for tests. Defaults to Date.now. */
|
|
19
|
+
now?: () => number;
|
|
20
|
+
/** RBF bump numerator/denominator. Defaults to 1125/1000 (+12.5%, above geth's 10% floor). */
|
|
21
|
+
bumpNum?: bigint;
|
|
22
|
+
bumpDen?: bigint;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Tracks settle txs by nonce so multiple settlements pipeline (a bounded window)
|
|
26
|
+
* and stuck ones can be replaced-by-fee. Knows nothing about games or settlement —
|
|
27
|
+
* a generic engine primitive (the relayer spec §13 deferred item). Process-local.
|
|
28
|
+
*/
|
|
29
|
+
export type PendingTxTracker = {
|
|
30
|
+
/** Reserve the next nonce, or undefined if the window is full. */
|
|
31
|
+
claim(): number | undefined;
|
|
32
|
+
/** Record the tx hash + fees we submitted for a claimed nonce. */
|
|
33
|
+
recordSubmission(nonce: number, tx: {
|
|
34
|
+
hash: string;
|
|
35
|
+
} & TxFees): void;
|
|
36
|
+
/** True if the tx for `nonce` was submitted longer than `staleMs` ago and is still pending. */
|
|
37
|
+
isStale(nonce: number, staleMs: number): boolean;
|
|
38
|
+
/** Compute strictly-higher fees for a replace-by-fee resubmission of `nonce`. */
|
|
39
|
+
bumpFees(nonce: number): TxFees;
|
|
40
|
+
/** Mark a nonce's tx mined; frees the slot and advances the window. */
|
|
41
|
+
markMined(nonce: number): void;
|
|
42
|
+
/** Current pending entries, for observability. */
|
|
43
|
+
pending(): readonly PendingTx[];
|
|
44
|
+
};
|
|
45
|
+
export declare const createPendingTxTracker: (opts: PendingTxTrackerOptions) => PendingTxTracker;
|
|
46
|
+
//# sourceMappingURL=pending-tx.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-tx.d.ts","sourceRoot":"","sources":["../../src/stores/pending-tx.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,MAAM,MAAM,MAAM,GAAG;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,oBAAoB,EAAE,MAAM,CAAA;CAC7B,CAAA;AAED,iEAAiE;AACjE,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAA;IAClB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAA;IACjB,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,kEAAkE;IAClE,KAAK,IAAI,MAAM,GAAG,SAAS,CAAA;IAC3B,kEAAkE;IAClE,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,GAAG,IAAI,CAAA;IACpE,+FAA+F;IAC/F,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;IAChD,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/B,uEAAuE;IACvE,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,kDAAkD;IAClD,OAAO,IAAI,SAAS,SAAS,EAAE,CAAA;CAChC,CAAA;AAID,eAAO,MAAM,sBAAsB,GAAI,MAAM,uBAAuB,KAAG,gBA2CtE,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const ceilMul = (v, num, den) => (v * num + den - 1n) / den;
|
|
2
|
+
export const createPendingTxTracker = (opts) => {
|
|
3
|
+
const now = opts.now ?? (() => Date.now());
|
|
4
|
+
const bumpNum = opts.bumpNum ?? 1125n;
|
|
5
|
+
const bumpDen = opts.bumpDen ?? 1000n;
|
|
6
|
+
const inFlight = new Map(); // null = claimed, not yet submitted
|
|
7
|
+
let nextNonce = opts.baseNonce;
|
|
8
|
+
const liveCount = () => inFlight.size;
|
|
9
|
+
return {
|
|
10
|
+
claim: () => {
|
|
11
|
+
if (liveCount() >= opts.windowSize)
|
|
12
|
+
return undefined;
|
|
13
|
+
const nonce = nextNonce;
|
|
14
|
+
nextNonce += 1;
|
|
15
|
+
inFlight.set(nonce, null);
|
|
16
|
+
return nonce;
|
|
17
|
+
},
|
|
18
|
+
recordSubmission: (nonce, tx) => {
|
|
19
|
+
inFlight.set(nonce, {
|
|
20
|
+
nonce,
|
|
21
|
+
hash: tx.hash,
|
|
22
|
+
fees: { maxFeePerGas: tx.maxFeePerGas, maxPriorityFeePerGas: tx.maxPriorityFeePerGas },
|
|
23
|
+
submittedAt: now(),
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
isStale: (nonce, staleMs) => {
|
|
27
|
+
const e = inFlight.get(nonce);
|
|
28
|
+
if (!e)
|
|
29
|
+
return false;
|
|
30
|
+
return now() - e.submittedAt > staleMs;
|
|
31
|
+
},
|
|
32
|
+
bumpFees: (nonce) => {
|
|
33
|
+
const e = inFlight.get(nonce);
|
|
34
|
+
if (!e)
|
|
35
|
+
throw new Error(`pending-tx: no submission recorded for nonce ${nonce}`);
|
|
36
|
+
return {
|
|
37
|
+
maxFeePerGas: ceilMul(e.fees.maxFeePerGas, bumpNum, bumpDen),
|
|
38
|
+
maxPriorityFeePerGas: ceilMul(e.fees.maxPriorityFeePerGas, bumpNum, bumpDen),
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
markMined: (nonce) => {
|
|
42
|
+
inFlight.delete(nonce);
|
|
43
|
+
},
|
|
44
|
+
pending: () => [...inFlight.values()].filter((e) => e !== null),
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=pending-tx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-tx.js","sourceRoot":"","sources":["../../src/stores/pending-tx.ts"],"names":[],"mappings":"AA8CA,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,GAAW,EAAE,GAAW,EAAU,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,CAAA;AAE3F,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,IAA6B,EAAoB,EAAE;IACxF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAA;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAA;IACrC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAA,CAAC,oCAAoC;IACzF,IAAI,SAAS,GAAG,IAAI,CAAC,SAAS,CAAA;IAE9B,MAAM,SAAS,GAAG,GAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAA;IAE7C,OAAO;QACL,KAAK,EAAE,GAAG,EAAE;YACV,IAAI,SAAS,EAAE,IAAI,IAAI,CAAC,UAAU;gBAAE,OAAO,SAAS,CAAA;YACpD,MAAM,KAAK,GAAG,SAAS,CAAA;YACvB,SAAS,IAAI,CAAC,CAAA;YACd,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;YACzB,OAAO,KAAK,CAAA;QACd,CAAC;QACD,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YAC9B,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE;gBAClB,KAAK;gBACL,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,IAAI,EAAE,EAAE,YAAY,EAAE,EAAE,CAAC,YAAY,EAAE,oBAAoB,EAAE,EAAE,CAAC,oBAAoB,EAAE;gBACtF,WAAW,EAAE,GAAG,EAAE;aACnB,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YAC1B,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YAC7B,IAAI,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAA;YACpB,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC,WAAW,GAAG,OAAO,CAAA;QACxC,CAAC;QACD,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YAClB,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YAC7B,IAAI,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,KAAK,EAAE,CAAC,CAAA;YAChF,OAAO;gBACL,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC;gBAC5D,oBAAoB,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,EAAE,OAAO,EAAE,OAAO,CAAC;aAC7E,CAAA;QACH,CAAC;QACD,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YACnB,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACxB,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAkB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;KAChF,CAAA;AACH,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@msgboard/relayer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"description": "Controllable, safe-by-default pool-watcher relayer for the msgboard board",
|
|
5
5
|
"repository": "github:valve-tech/msgboard",
|
|
6
6
|
"author": "MsgBoard",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"prebuild": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
31
31
|
"build": "tsc",
|
|
32
32
|
"watch": "tsc -w",
|
|
33
|
+
"typecheck": "tsc --noEmit -p tsconfig.test.json",
|
|
33
34
|
"test": "vitest run",
|
|
34
35
|
"test:watch": "vitest",
|
|
35
36
|
"lint": "prettier --check ."
|
|
@@ -38,8 +39,8 @@
|
|
|
38
39
|
"dist/"
|
|
39
40
|
],
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"@msgboard/history": "^0.0.
|
|
42
|
-
"@msgboard/sdk": "^0.0.
|
|
42
|
+
"@msgboard/history": "^0.0.32",
|
|
43
|
+
"@msgboard/sdk": "^0.0.32",
|
|
43
44
|
"viem": "^2.25.0"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|