@verify-network/protocol 0.0.0-stage → 0.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/fee.js ADDED
@@ -0,0 +1,142 @@
1
+ import { remainingDelay } from "./request.js";
2
+ import { parseGwei, size } from "viem";
3
+ //#region src/fee.ts
4
+ /** The fees a node's sender never goes below unless configured otherwise; tuned for Base. */
5
+ const DEFAULT_FEE_FLOOR = {
6
+ maxFeePerGas: parseGwei("0.005"),
7
+ maxPriorityFeePerGas: parseGwei("0.001")
8
+ };
9
+ /** Headroom over the base fee so a send stays mineable while the base fee climbs between refreshes. */
10
+ const BASE_FEE_MULTIPLIER = 2n;
11
+ /**
12
+ * The fees a node's sender bids given the latest base fee: twice the base fee plus the tip, never
13
+ * under the floor. Here because a signer mirrors it: the enclave refuses an authorization whose
14
+ * maximum gas price is under this bid (ADR 0028), so a client sizes its maximum from it.
15
+ */
16
+ const computeFees = (baseFeePerGas, floor = DEFAULT_FEE_FLOOR) => {
17
+ const headroom = baseFeePerGas * BASE_FEE_MULTIPLIER + floor.maxPriorityFeePerGas;
18
+ return {
19
+ maxFeePerGas: headroom > floor.maxFeePerGas ? headroom : floor.maxFeePerGas,
20
+ maxPriorityFeePerGas: floor.maxPriorityFeePerGas
21
+ };
22
+ };
23
+ /**
24
+ * `Config.CALLDATA_GAS_PER_BYTE`: EIP-7623's calldata floor for a non-zero byte, charged for every
25
+ * echoed byte (ADR 0023).
26
+ */
27
+ const CALLDATA_GAS_PER_BYTE = 40n;
28
+ /**
29
+ * The gas price a request locks, mirroring the router's: the request transaction's gas price, never
30
+ * below the base fee of the block it lands in (ADR 0019). An OP-stack L1→L2 deposit transaction
31
+ * runs at a zero gas price; priced at that, its request would reserve nothing and its fulfillment
32
+ * charge nothing.
33
+ */
34
+ const lockedGasPrice = ({ gasPrice, baseFeePerGas }) => gasPrice > baseFeePerGas ? gasPrice : baseFeePerGas;
35
+ /**
36
+ * The fee a request locks at request time (ADR 0013, 0019, 0023):
37
+ * `(callbackGasLimit + fulfillmentOverheadGas + 40 × callbackDataLength) × lockedGasPrice × (100% + premium)`,
38
+ * rounded down to the wei, where the locked gas price is `gasPrice` floored at `baseFeePerGas`
39
+ * (`lockedGasPrice`). Mirrors the router's fee, so the router must multiply before it divides by
40
+ * 100 too. Gas amounts and the premium are accepted as `number` so a decoded request can be priced
41
+ * without conversion.
42
+ */
43
+ const fee = ({ callbackGasLimit, fulfillmentOverheadGas, callbackDataLength, gasPrice, baseFeePerGas, premiumPercent }) => (BigInt(callbackGasLimit) + BigInt(fulfillmentOverheadGas) + CALLDATA_GAS_PER_BYTE * BigInt(callbackDataLength)) * lockedGasPrice({
44
+ gasPrice,
45
+ baseFeePerGas
46
+ }) * (100n + BigInt(premiumPercent)) / 100n;
47
+ /**
48
+ * The most a direct request can ever charge (ADR 0028): the reservation formula at the maximum
49
+ * gas price the authorization names, with the same overhead and premium as an on-chain request.
50
+ * Mirrors the router's ceiling, which debits it from the signer's balance before the callback and
51
+ * credits back what the settlement does not take.
52
+ */
53
+ const ceiling = ({ callbackGasLimit, fulfillmentOverheadGas, callbackDataLength, maxGasPrice, premiumPercent }) => fee({
54
+ callbackGasLimit,
55
+ fulfillmentOverheadGas,
56
+ callbackDataLength,
57
+ gasPrice: maxGasPrice,
58
+ baseFeePerGas: 0n,
59
+ premiumPercent
60
+ });
61
+ /**
62
+ * What a fulfillment is owed for the gas it took, mirroring the router's settlement: that gas at
63
+ * the price the request locked, plus the premium, and never more than the request reserved. The
64
+ * submitting transaction's gas price does not change it (ADR 0019). The premium decays in a
65
+ * straight line with the time since the assignment (ADR 0029): whole when `settledAt`, the
66
+ * router's `block.timestamp`, is `assignedAt`, and nothing once the request is late (ADR 0025),
67
+ * so the same timing decides the quote here and on the router:
68
+ * `gasUsed × lockedGasPrice × (100 × D + premiumPercent × remaining) / (100 × D)`, with `D` the
69
+ * stale delay and `remaining` the seconds left of it (`remainingDelay`). Everything is multiplied
70
+ * before the one division, as on the router, so both round down to the same wei. The rest of the
71
+ * reservation goes back to the consumer.
72
+ */
73
+ const settlement = ({ gasUsed, requestGasPrice, premiumPercent, fee: reserved, assignedAt, settledAt }) => settlementAtShare({
74
+ gasUsed,
75
+ requestGasPrice,
76
+ premiumPercent,
77
+ fee: reserved,
78
+ share: premiumShare({
79
+ assignedAt,
80
+ settledAt
81
+ })
82
+ });
83
+ /** The whole premium: what the assignment's block pays, and a direct request whenever it lands. */
84
+ const WHOLE_PREMIUM = {
85
+ remaining: BigInt(30),
86
+ delay: BigInt(30)
87
+ };
88
+ /**
89
+ * The share of the premium a fulfillment settled at `settledAt`, the router's `block.timestamp`,
90
+ * was paid for a request assigned at `assignedAt`. `RequestSettled` does not carry it, so a reader
91
+ * of a settlement works it out from the two times, by the same `remainingDelay` the charge uses.
92
+ */
93
+ const premiumShare = ({ assignedAt, settledAt }) => ({
94
+ remaining: remainingDelay({
95
+ assignedAt,
96
+ at: settledAt
97
+ }),
98
+ delay: BigInt(30)
99
+ });
100
+ /**
101
+ * `settlement` at a share of the premium already worked out, as the router's own takes the
102
+ * seconds left: `gasUsed × lockedGasPrice × (100 × delay + premiumPercent × remaining) / (100 × delay)`,
103
+ * capped by the reservation.
104
+ */
105
+ const settlementAtShare = ({ gasUsed, requestGasPrice, premiumPercent, fee: reserved, share: { remaining, delay } }) => {
106
+ const amount = BigInt(gasUsed) * requestGasPrice * (100n * delay + BigInt(premiumPercent) * remaining) / (100n * delay);
107
+ return amount > reserved ? reserved : amount;
108
+ };
109
+ /**
110
+ * The gas a charge was computed from, which `RequestSettled` does not carry: the charge divided
111
+ * back by the locked price and the premium's share, rounded up, since the charge was rounded down
112
+ * by less than one gas's worth. `settlementAtShare` on it reproduces the charge to the wei. A
113
+ * charge the reservation capped says only that the gas was at least this; a gas price of nothing
114
+ * says nothing of the gas.
115
+ */
116
+ const chargedGas = ({ charged, requestGasPrice, premiumPercent, share: { remaining, delay } }) => {
117
+ const perGas = requestGasPrice * (100n * delay + BigInt(premiumPercent) * remaining);
118
+ return perGas === 0n ? 0n : (charged * 100n * delay + perGas - 1n) / perGas;
119
+ };
120
+ /** Transaction intrinsic gas. */
121
+ const INTRINSIC_GAS = 21000n;
122
+ /**
123
+ * What the router spends around the callback: the diamond dispatch, recomputing the request
124
+ * id, recovering the enclave signature, closing the request, and crediting the operator before
125
+ * it, the event after it. Measured under 60k besides the callback (`GasBudget.test.ts`); the
126
+ * margin covers cold storage slots, and in a batch the array encoding around each item.
127
+ */
128
+ const FULFILLMENT_OVERHEAD_GAS = 150000n;
129
+ /** `Config.FULFILLMENT_POST_CALLBACK_GAS`: what the router keeps back for after the callback. */
130
+ const FULFILLMENT_POST_CALLBACK_GAS = 80000n;
131
+ /**
132
+ * The gas one fulfillment adds to a transaction, given the calldata that carries it, every byte
133
+ * at EIP-7623's non-zero floor (`CALLDATA_GAS_PER_BYTE`), below which the chain refuses a
134
+ * transaction whose execution is small beside its calldata (ADR 0023). The router
135
+ * reverts `InsufficientFulfillmentGas` unless `gasleft()` at the callback covers the callback gas
136
+ * limit scaled by 64/63 (EIP-150 keeps a 64th back from a call) plus what it needs afterwards, so
137
+ * the item needs that requirement plus everything spent before the callback. A batch's limit is
138
+ * the intrinsic gas plus every item's share.
139
+ */
140
+ const fulfillmentItemGas = ({ callbackGasLimit, data }) => CALLDATA_GAS_PER_BYTE * BigInt(size(data)) + FULFILLMENT_OVERHEAD_GAS + BigInt(callbackGasLimit) * 64n / 63n + FULFILLMENT_POST_CALLBACK_GAS;
141
+ //#endregion
142
+ export { CALLDATA_GAS_PER_BYTE, DEFAULT_FEE_FLOOR, INTRINSIC_GAS, WHOLE_PREMIUM, ceiling, chargedGas, computeFees, fee, fulfillmentItemGas, lockedGasPrice, premiumShare, settlement, settlementAtShare };
@@ -0,0 +1,32 @@
1
+ import { t as RouterDomain } from "./domain-CXJsoNRU.js";
2
+ import { Hex } from "viem";
3
+ //#region src/fulfillment.d.ts
4
+ /** The typed data an enclave signs to deliver a fulfillment, as viem's `signTypedData` takes it. */
5
+ export declare const fulfillmentTypedData: ({ chainId, router, requestId, proofCommitment }: RouterDomain & {
6
+ readonly requestId: Hex;
7
+ readonly proofCommitment: Hex;
8
+ }) => {
9
+ readonly domain: {
10
+ readonly name: "VerifyNetworkVRF";
11
+ readonly version: "2";
12
+ readonly chainId: number | bigint;
13
+ readonly verifyingContract: `0x${string}`;
14
+ };
15
+ readonly types: {
16
+ readonly Fulfillment: readonly [{
17
+ readonly name: "requestId";
18
+ readonly type: "bytes32";
19
+ }, {
20
+ readonly name: "proofCommitment";
21
+ readonly type: "bytes32";
22
+ }];
23
+ };
24
+ readonly primaryType: "Fulfillment";
25
+ readonly message: {
26
+ readonly requestId: `0x${string}`;
27
+ readonly proofCommitment: `0x${string}`;
28
+ };
29
+ };
30
+ /** Mirrors `Signatures.fulfillmentDigest` in the router: the hash the enclave signature is recovered against. */
31
+ export declare const fulfillmentDigest: (fulfillment: Parameters<typeof fulfillmentTypedData>[0]) => Hex;
32
+ //#endregion
@@ -0,0 +1,28 @@
1
+ import { hashTypedData } from "viem";
2
+ //#region src/fulfillment.ts
3
+ const fulfillmentTypes = { Fulfillment: [{
4
+ name: "requestId",
5
+ type: "bytes32"
6
+ }, {
7
+ name: "proofCommitment",
8
+ type: "bytes32"
9
+ }] };
10
+ /** The typed data an enclave signs to deliver a fulfillment, as viem's `signTypedData` takes it. */
11
+ const fulfillmentTypedData = ({ chainId, router, requestId, proofCommitment }) => ({
12
+ domain: {
13
+ name: "VerifyNetworkVRF",
14
+ version: "2",
15
+ chainId,
16
+ verifyingContract: router
17
+ },
18
+ types: fulfillmentTypes,
19
+ primaryType: "Fulfillment",
20
+ message: {
21
+ requestId,
22
+ proofCommitment
23
+ }
24
+ });
25
+ /** Mirrors `Signatures.fulfillmentDigest` in the router: the hash the enclave signature is recovered against. */
26
+ const fulfillmentDigest = (fulfillment) => hashTypedData(fulfillmentTypedData(fulfillment));
27
+ //#endregion
28
+ export { fulfillmentDigest, fulfillmentTypedData };