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