@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/LICENSE +21 -0
- package/dist/domain-CXJsoNRU.d.ts +9 -0
- package/dist/ecvrf.d.ts +69 -0
- package/dist/ecvrf.js +174 -0
- package/dist/event.d.ts +101 -0
- package/dist/event.js +53 -0
- package/dist/fee.d.ts +142 -0
- package/dist/fee.js +142 -0
- package/dist/fulfillment.d.ts +32 -0
- package/dist/fulfillment.js +28 -0
- package/dist/index.d.ts +708 -0
- package/dist/index.js +872 -0
- package/dist/request.d.ts +60 -0
- package/dist/request.js +61 -0
- package/package.json +56 -4
- package/README.md +0 -3
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 };
|