@livo-build/runtime 0.1.0 → 0.2.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/chain.d.ts +7 -0
- package/dist/chain.js +10 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/relayer.d.ts +20 -0
- package/dist/relayer.js +54 -0
- package/package.json +1 -1
package/dist/chain.d.ts
CHANGED
|
@@ -94,6 +94,13 @@ export declare class Chain {
|
|
|
94
94
|
*/
|
|
95
95
|
send(opts: SendOptions): Promise<Hex>;
|
|
96
96
|
pendingNonce(address: Hex): Promise<bigint>;
|
|
97
|
+
/**
|
|
98
|
+
* The nonce to use for the next send (when the caller didn't pin one). Defaults
|
|
99
|
+
* to the RPC pending count — fine for a single signer. Subclasses (see Relayer)
|
|
100
|
+
* override this to serialize nonces across concurrent invocations that share one
|
|
101
|
+
* managed signing key, which the per-Worker RPC count alone can't coordinate.
|
|
102
|
+
*/
|
|
103
|
+
protected nextNonce(address: Hex): Promise<bigint>;
|
|
97
104
|
/** Compute maxFee/tip: maxFee = baseFee*2 + tip, tip = configurable default. */
|
|
98
105
|
feeData(): Promise<{
|
|
99
106
|
maxFeePerGas: bigint;
|
package/dist/chain.js
CHANGED
|
@@ -113,7 +113,7 @@ export class Chain {
|
|
|
113
113
|
const value = opts.value ?? 0n;
|
|
114
114
|
const [chainId, nonce, fees] = await Promise.all([
|
|
115
115
|
this.getChainId(),
|
|
116
|
-
opts.nonce !== undefined ? Promise.resolve(opts.nonce) : this.
|
|
116
|
+
opts.nonce !== undefined ? Promise.resolve(opts.nonce) : this.nextNonce(this.address),
|
|
117
117
|
this.feeData(),
|
|
118
118
|
]);
|
|
119
119
|
const gas = opts.gas ?? (await this.estimateGas({ to: opts.address, from: this.address, data, value }));
|
|
@@ -139,6 +139,15 @@ export class Chain {
|
|
|
139
139
|
async pendingNonce(address) {
|
|
140
140
|
return BigInt(await this.rpc("eth_getTransactionCount", [address, "pending"]));
|
|
141
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* The nonce to use for the next send (when the caller didn't pin one). Defaults
|
|
144
|
+
* to the RPC pending count — fine for a single signer. Subclasses (see Relayer)
|
|
145
|
+
* override this to serialize nonces across concurrent invocations that share one
|
|
146
|
+
* managed signing key, which the per-Worker RPC count alone can't coordinate.
|
|
147
|
+
*/
|
|
148
|
+
nextNonce(address) {
|
|
149
|
+
return this.pendingNonce(address);
|
|
150
|
+
}
|
|
142
151
|
/** Compute maxFee/tip: maxFee = baseFee*2 + tip, tip = configurable default. */
|
|
143
152
|
async feeData() {
|
|
144
153
|
const tip = this.opts.defaultTipWei;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { Chain } from "./chain.js";
|
|
2
2
|
export type { ChainOptions, SendOptions, ReadOptions, GetLogsOptions, Receipt, } from "./chain.js";
|
|
3
|
+
export { Relayer } from "./relayer.js";
|
|
4
|
+
export type { RelayerOptions } from "./relayer.js";
|
|
3
5
|
export { Store, STORE_TABLE } from "./store.js";
|
|
4
6
|
export type { D1Like } from "./store.js";
|
|
5
7
|
export { log } from "./log.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
// tree-shakeable exports; keep the core small and auditable (it may hold keys).
|
|
4
4
|
// Layer 1 — Chain: sign + send + read with zero hand-rolled crypto/ABI.
|
|
5
5
|
export { Chain } from "./chain.js";
|
|
6
|
+
// Relayer — managed signing for bots (custodied RELAYER_PRIVATE_KEY + optional
|
|
7
|
+
// Convex-serialized nonces). A Chain that defaults to the relayer key.
|
|
8
|
+
export { Relayer } from "./relayer.js";
|
|
6
9
|
// Layer 3 — State + utilities.
|
|
7
10
|
export { Store, STORE_TABLE } from "./store.js";
|
|
8
11
|
export { log } from "./log.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Chain, type ChainOptions } from "./chain.js";
|
|
2
|
+
import type { Hex } from "./hex.js";
|
|
3
|
+
export interface RelayerOptions extends ChainOptions {
|
|
4
|
+
/** Convex /relayer/nonce endpoint. Default: env RELAYER_NONCE_URL. */
|
|
5
|
+
nonceUrl?: string;
|
|
6
|
+
/** Bearer scoping the nonce allocation to this bot. Default: env RELAYER_NONCE_TOKEN. */
|
|
7
|
+
nonceToken?: string;
|
|
8
|
+
}
|
|
9
|
+
interface MinimalEnv {
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
export declare class Relayer extends Chain {
|
|
13
|
+
private readonly nonceUrl?;
|
|
14
|
+
private readonly nonceToken?;
|
|
15
|
+
constructor(env: MinimalEnv | undefined, options?: RelayerOptions);
|
|
16
|
+
/** True when this relayer will serialize nonces through Convex (vs. RPC pending). */
|
|
17
|
+
get serializesNonce(): boolean;
|
|
18
|
+
protected nextNonce(address: Hex): Promise<bigint>;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
package/dist/relayer.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Relayer — the managed signing layer for bots. A bot's relayer wallet is a
|
|
2
|
+
// platform-custodied key (a SEPARATE key from the project's keeper, so a bot and
|
|
3
|
+
// a keeper never collide on the same address's nonce), injected at deploy as the
|
|
4
|
+
// RELAYER_PRIVATE_KEY binding. So a bot just does `new Relayer(env).send(...)` —
|
|
5
|
+
// no key handling, no faucet ritual, no hand-rolled crypto.
|
|
6
|
+
//
|
|
7
|
+
// When the platform also injects RELAYER_NONCE_URL + RELAYER_NONCE_TOKEN, the
|
|
8
|
+
// relayer asks Convex for a *serialized* nonce before each send. Convex mutations
|
|
9
|
+
// are serializable, so two concurrent bot invocations sharing one relayer key get
|
|
10
|
+
// strictly increasing nonces instead of both reading the same RPC pending count
|
|
11
|
+
// and colliding. With the bindings absent (e.g. local dev), it transparently
|
|
12
|
+
// falls back to the RPC pending count — same behavior as a plain Chain.
|
|
13
|
+
import { Chain } from "./chain.js";
|
|
14
|
+
export class Relayer extends Chain {
|
|
15
|
+
nonceUrl;
|
|
16
|
+
nonceToken;
|
|
17
|
+
constructor(env, options = {}) {
|
|
18
|
+
const { nonceUrl, nonceToken, ...chainOpts } = options;
|
|
19
|
+
// The relayer key is the signer by default — NOT the keeper key.
|
|
20
|
+
super(env, { signerKeySecret: chainOpts.signerKeySecret ?? "RELAYER_PRIVATE_KEY", ...chainOpts });
|
|
21
|
+
this.nonceUrl = nonceUrl ?? env?.RELAYER_NONCE_URL;
|
|
22
|
+
this.nonceToken = nonceToken ?? env?.RELAYER_NONCE_TOKEN;
|
|
23
|
+
}
|
|
24
|
+
/** True when this relayer will serialize nonces through Convex (vs. RPC pending). */
|
|
25
|
+
get serializesNonce() {
|
|
26
|
+
return Boolean(this.nonceUrl && this.nonceToken);
|
|
27
|
+
}
|
|
28
|
+
// Ask Convex for the next serialized nonce, passing the RPC pending count so the
|
|
29
|
+
// server-side counter self-heals when the wallet advanced out-of-band. Any
|
|
30
|
+
// failure (no bindings, network, non-200) falls back to the RPC pending count —
|
|
31
|
+
// the relayer never blocks on the coordinator being reachable.
|
|
32
|
+
async nextNonce(address) {
|
|
33
|
+
const observedPending = await this.pendingNonce(address);
|
|
34
|
+
if (!this.nonceUrl || !this.nonceToken)
|
|
35
|
+
return observedPending;
|
|
36
|
+
try {
|
|
37
|
+
const chainId = await this.getChainId();
|
|
38
|
+
const res = await fetch(this.nonceUrl, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${this.nonceToken}` },
|
|
41
|
+
body: JSON.stringify({ address, chainId, observedPending: Number(observedPending) }),
|
|
42
|
+
});
|
|
43
|
+
if (res.ok) {
|
|
44
|
+
const j = (await res.json());
|
|
45
|
+
if (j.ok && typeof j.nonce === "number" && j.nonce >= 0)
|
|
46
|
+
return BigInt(j.nonce);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* fall through to the RPC pending count */
|
|
51
|
+
}
|
|
52
|
+
return observedPending;
|
|
53
|
+
}
|
|
54
|
+
}
|