@hammerock/cca-sdk 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/README.md +145 -0
- package/dist/chunk-ABCX23D6.js +446 -0
- package/dist/chunk-ABCX23D6.js.map +1 -0
- package/dist/erc20-C3yff839.d.cts +231 -0
- package/dist/erc20-C3yff839.d.ts +231 -0
- package/dist/index.cjs +998 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2717 -0
- package/dist/index.d.ts +2717 -0
- package/dist/index.js +485 -0
- package/dist/index.js.map +1 -0
- package/dist/node.cjs +448 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +97 -0
- package/dist/node.d.ts +97 -0
- package/dist/node.js +277 -0
- package/dist/node.js.map +1 -0
- package/package.json +84 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { erc20MetadataAbi, MPS, SUPPORTED_CHAINS, FACTORY_DEPLOY_BLOCK, BLOCK_TIME_SECONDS, FACTORY_ADDRESS, auctionAbi, PERMIT2_ADDRESS, NATIVE_CURRENCY } from './chunk-ABCX23D6.js';
|
|
2
|
+
export { BLOCK_TIME_SECONDS, DEFAULT_LOG_CHUNK_SIZE, FACTORY_ADDRESS, FACTORY_DEPLOY_BLOCK, LENS_OVERRIDE_ADDRESS, MPS, NATIVE_CURRENCY, PERMIT2_ADDRESS, Q96, SUPPORTED_CHAINS, UPSTREAM_COMMIT, UPSTREAM_TAG, X7, auctionAbi, auctionCreatedEvent, auctionStateLensAbi, auctionStateLensDeployedBytecode, decimalToQ96, deriveStatus, erc20MetadataAbi, factoryAbi, formatMpsPercent, formatQ96, formatQ96Compact, formatTokenAmount, fromValueX7, getAuctionInfo, getAuctionListStates, getAuctionState, getAuctionSummaries, getAuctionSummary, getClearingPrice, getTokenMetadata, getTokenMetadataBatch, isFailedSummary, isGraduated, mpsToFraction, q96ToDecimalString, q96ToNumber, scanAuctions, scanAuctionsForToken, watchAuctions } from './chunk-ABCX23D6.js';
|
|
3
|
+
import { parseAbiParameters, maxUint256, encodeAbiParameters, decodeAbiParameters, createPublicClient, http, encodeFunctionData } from 'viem';
|
|
4
|
+
|
|
5
|
+
var MIN_FLOOR_PRICE = 4294967296n;
|
|
6
|
+
var MIN_TICK_SPACING = 2n;
|
|
7
|
+
var UINT24_MAX = 16777215;
|
|
8
|
+
var UINT40_MAX = 1099511627775;
|
|
9
|
+
var AuctionParametersError = class extends Error {
|
|
10
|
+
};
|
|
11
|
+
function packAuctionSteps(steps) {
|
|
12
|
+
if (steps.length === 0) {
|
|
13
|
+
throw new AuctionParametersError("auctionStepsData cannot be empty (InvalidAuctionDataLength).");
|
|
14
|
+
}
|
|
15
|
+
let packed = "";
|
|
16
|
+
for (const step of steps) {
|
|
17
|
+
if (!Number.isInteger(step.mps) || step.mps < 0 || step.mps > UINT24_MAX) {
|
|
18
|
+
throw new AuctionParametersError(`step mps ${step.mps} does not fit uint24.`);
|
|
19
|
+
}
|
|
20
|
+
if (!Number.isInteger(step.blockDelta) || step.blockDelta <= 0) {
|
|
21
|
+
throw new AuctionParametersError(`step blockDelta must be > 0, got ${step.blockDelta}.`);
|
|
22
|
+
}
|
|
23
|
+
if (step.blockDelta > UINT40_MAX) {
|
|
24
|
+
throw new AuctionParametersError(`step blockDelta ${step.blockDelta} does not fit uint40.`);
|
|
25
|
+
}
|
|
26
|
+
packed += step.mps.toString(16).padStart(6, "0");
|
|
27
|
+
packed += step.blockDelta.toString(16).padStart(10, "0");
|
|
28
|
+
}
|
|
29
|
+
return `0x${packed}`;
|
|
30
|
+
}
|
|
31
|
+
function unpackAuctionSteps(data) {
|
|
32
|
+
const body = data.slice(2);
|
|
33
|
+
if (body.length === 0 || body.length % 16 !== 0) {
|
|
34
|
+
throw new AuctionParametersError(
|
|
35
|
+
`auctionStepsData must be a whole number of 8-byte steps, got ${body.length / 2} bytes.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const steps = [];
|
|
39
|
+
for (let i = 0; i < body.length; i += 16) {
|
|
40
|
+
steps.push({
|
|
41
|
+
mps: Number.parseInt(body.slice(i, i + 6), 16),
|
|
42
|
+
blockDelta: Number.parseInt(body.slice(i + 6, i + 16), 16)
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return steps;
|
|
46
|
+
}
|
|
47
|
+
function buildAuctionSteps(durationBlocks) {
|
|
48
|
+
if (!Number.isInteger(durationBlocks) || durationBlocks < 2) {
|
|
49
|
+
throw new AuctionParametersError(`durationBlocks must be an integer >= 2, got ${durationBlocks}.`);
|
|
50
|
+
}
|
|
51
|
+
const total = Number(MPS);
|
|
52
|
+
const perBlock = Math.floor(total / durationBlocks);
|
|
53
|
+
if (perBlock <= 0) {
|
|
54
|
+
throw new AuctionParametersError(
|
|
55
|
+
`durationBlocks ${durationBlocks} is longer than MPS (${total}) allows - each block would release 0.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const remainder = total - perBlock * durationBlocks;
|
|
59
|
+
const finalBlockMps = perBlock + remainder;
|
|
60
|
+
if (finalBlockMps > UINT24_MAX) {
|
|
61
|
+
throw new AuctionParametersError(`final step mps ${finalBlockMps} does not fit uint24.`);
|
|
62
|
+
}
|
|
63
|
+
return durationBlocks === 1 ? [{ mps: total, blockDelta: 1 }] : [
|
|
64
|
+
{ mps: perBlock, blockDelta: durationBlocks - 1 },
|
|
65
|
+
{ mps: finalBlockMps, blockDelta: 1 }
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
function totalMps(steps) {
|
|
69
|
+
return steps.reduce((sum, step) => sum + BigInt(step.mps) * BigInt(step.blockDelta), 0n);
|
|
70
|
+
}
|
|
71
|
+
function totalBlocks(steps) {
|
|
72
|
+
return steps.reduce((sum, step) => sum + BigInt(step.blockDelta), 0n);
|
|
73
|
+
}
|
|
74
|
+
function validateAuctionParameters(params) {
|
|
75
|
+
if (params.startBlock >= params.endBlock) {
|
|
76
|
+
throw new AuctionParametersError(
|
|
77
|
+
`startBlock (${params.startBlock}) must be < endBlock (${params.endBlock}) - InvalidEndBlock.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (params.claimBlock < params.endBlock) {
|
|
81
|
+
throw new AuctionParametersError(
|
|
82
|
+
`claimBlock (${params.claimBlock}) must be >= endBlock (${params.endBlock}) - ClaimBlockIsBeforeEndBlock.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (params.tickSpacing < MIN_TICK_SPACING) {
|
|
86
|
+
throw new AuctionParametersError(
|
|
87
|
+
`tickSpacing (${params.tickSpacing}) must be >= ${MIN_TICK_SPACING} - TickSpacingTooSmall.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (params.floorPriceQ96 === 0n) {
|
|
91
|
+
throw new AuctionParametersError("floorPrice cannot be zero - FloorPriceIsZero.");
|
|
92
|
+
}
|
|
93
|
+
if (params.floorPriceQ96 < MIN_FLOOR_PRICE) {
|
|
94
|
+
throw new AuctionParametersError(
|
|
95
|
+
`floorPrice (${params.floorPriceQ96}) must be >= ${MIN_FLOOR_PRICE} - FloorPriceTooLow.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (params.floorPriceQ96 % params.tickSpacing !== 0n) {
|
|
99
|
+
throw new AuctionParametersError(
|
|
100
|
+
`floorPrice (${params.floorPriceQ96}) must be a multiple of tickSpacing (${params.tickSpacing}) - TickPriceNotAtBoundary.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const steps = unpackAuctionSteps(params.auctionStepsData);
|
|
104
|
+
const mpsSum = totalMps(steps);
|
|
105
|
+
if (mpsSum !== MPS) {
|
|
106
|
+
throw new AuctionParametersError(
|
|
107
|
+
`supply schedule releases ${mpsSum} mps, must be exactly ${MPS} - InvalidStepDataMps.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const blockSum = totalBlocks(steps);
|
|
111
|
+
if (params.startBlock + blockSum !== params.endBlock) {
|
|
112
|
+
throw new AuctionParametersError(
|
|
113
|
+
`supply schedule spans ${blockSum} blocks, so it ends at ${params.startBlock + blockSum}, but endBlock is ${params.endBlock} - InvalidEndBlockGivenStepData.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
var PARAMETERS_TUPLE = parseAbiParameters(
|
|
118
|
+
"(address currency, address tokensRecipient, address fundsRecipient, uint64 startBlock, uint64 endBlock, uint64 claimBlock, uint256 tickSpacing, address validationHook, uint256 floorPrice, uint128 requiredCurrencyRaised, bytes auctionStepsData)"
|
|
119
|
+
);
|
|
120
|
+
function encodeAuctionParameters(params) {
|
|
121
|
+
validateAuctionParameters(params);
|
|
122
|
+
return encodeAbiParameters(PARAMETERS_TUPLE, [
|
|
123
|
+
{
|
|
124
|
+
currency: params.currency,
|
|
125
|
+
tokensRecipient: params.tokensRecipient,
|
|
126
|
+
fundsRecipient: params.fundsRecipient,
|
|
127
|
+
startBlock: params.startBlock,
|
|
128
|
+
endBlock: params.endBlock,
|
|
129
|
+
claimBlock: params.claimBlock,
|
|
130
|
+
tickSpacing: params.tickSpacing,
|
|
131
|
+
validationHook: params.validationHook,
|
|
132
|
+
floorPrice: params.floorPriceQ96,
|
|
133
|
+
requiredCurrencyRaised: params.requiredCurrencyRaised,
|
|
134
|
+
auctionStepsData: params.auctionStepsData
|
|
135
|
+
}
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
function decodeAuctionParameters(configData) {
|
|
139
|
+
const [decoded] = decodeAbiParameters(PARAMETERS_TUPLE, configData);
|
|
140
|
+
const value = decoded;
|
|
141
|
+
return {
|
|
142
|
+
currency: value.currency,
|
|
143
|
+
tokensRecipient: value.tokensRecipient,
|
|
144
|
+
fundsRecipient: value.fundsRecipient,
|
|
145
|
+
startBlock: BigInt(value.startBlock),
|
|
146
|
+
endBlock: BigInt(value.endBlock),
|
|
147
|
+
claimBlock: BigInt(value.claimBlock),
|
|
148
|
+
tickSpacing: value.tickSpacing,
|
|
149
|
+
validationHook: value.validationHook,
|
|
150
|
+
floorPriceQ96: value.floorPrice,
|
|
151
|
+
requiredCurrencyRaised: value.requiredCurrencyRaised,
|
|
152
|
+
auctionStepsData: value.auctionStepsData
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function assertSupportedChain(chainId) {
|
|
156
|
+
const chain = SUPPORTED_CHAINS[chainId];
|
|
157
|
+
if (!chain) {
|
|
158
|
+
const supported = Object.values(SUPPORTED_CHAINS).map((c) => `${c.name} (${c.id})`).join(", ");
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Beacon is testnet-only and refuses to run against chain id ${chainId}. Supported: ${supported}.`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return chain;
|
|
164
|
+
}
|
|
165
|
+
function createBeaconClient(config) {
|
|
166
|
+
const chain = assertSupportedChain(config.chainId);
|
|
167
|
+
const publicClient = createPublicClient({
|
|
168
|
+
chain,
|
|
169
|
+
transport: http(config.rpcUrl),
|
|
170
|
+
// Batch eagerly: reads fan out across many auctions in the directory.
|
|
171
|
+
batch: { multicall: true }
|
|
172
|
+
});
|
|
173
|
+
const logsClient = config.logsRpcUrl && config.logsRpcUrl !== config.rpcUrl ? createPublicClient({ chain, transport: http(config.logsRpcUrl) }) : publicClient;
|
|
174
|
+
const factoryDeployBlock = config.factoryDeployBlock ?? FACTORY_DEPLOY_BLOCK[config.chainId];
|
|
175
|
+
if (factoryDeployBlock === void 0) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`No known factory deploy block for chain id ${config.chainId}. Pass \`factoryDeployBlock\` explicitly so discovery knows where to start scanning.`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
publicClient,
|
|
182
|
+
logsClient,
|
|
183
|
+
chain,
|
|
184
|
+
chainId: config.chainId,
|
|
185
|
+
factoryAddress: config.factoryAddress ?? FACTORY_ADDRESS,
|
|
186
|
+
lensAddress: config.lensAddress,
|
|
187
|
+
factoryDeployBlock,
|
|
188
|
+
blockTimeSeconds: BLOCK_TIME_SECONDS[config.chainId] ?? 12
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async function assertChainMatches(client) {
|
|
192
|
+
const actual = await client.publicClient.getChainId();
|
|
193
|
+
if (actual !== client.chainId) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`RPC chain id mismatch: configured for ${client.chainId} (${client.chain.name}) but the endpoint reports ${actual}. Check BEACON_RPC_URL.`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/privateKey.ts
|
|
201
|
+
var PrivateKeyFormatError = class extends Error {
|
|
202
|
+
};
|
|
203
|
+
function normalizePrivateKey(raw, envVarName) {
|
|
204
|
+
const trimmed = (raw ?? "").trim();
|
|
205
|
+
if (trimmed.length === 0) {
|
|
206
|
+
throw new PrivateKeyFormatError(`${envVarName} is not set.`);
|
|
207
|
+
}
|
|
208
|
+
const body = trimmed.startsWith("0x") || trimmed.startsWith("0X") ? trimmed.slice(2) : trimmed;
|
|
209
|
+
if (!/^[0-9a-fA-F]{64}$/.test(body)) {
|
|
210
|
+
throw new PrivateKeyFormatError(
|
|
211
|
+
`${envVarName} is malformed: expected 64 hex characters (optionally 0x-prefixed), got ${body.length} characters. The value itself is not shown.`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (/^0{64}$/.test(body)) {
|
|
215
|
+
throw new PrivateKeyFormatError(
|
|
216
|
+
`${envVarName} is all zeros - that is the .env.example placeholder, not a real key.`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return `0x${body.toLowerCase()}`;
|
|
220
|
+
}
|
|
221
|
+
function isValidPrivateKey(raw) {
|
|
222
|
+
try {
|
|
223
|
+
normalizePrivateKey(raw, "key");
|
|
224
|
+
return true;
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
var erc20ApprovalAbi = [
|
|
230
|
+
...erc20MetadataAbi,
|
|
231
|
+
{
|
|
232
|
+
type: "function",
|
|
233
|
+
name: "allowance",
|
|
234
|
+
inputs: [
|
|
235
|
+
{ name: "owner", type: "address" },
|
|
236
|
+
{ name: "spender", type: "address" }
|
|
237
|
+
],
|
|
238
|
+
outputs: [{ type: "uint256" }],
|
|
239
|
+
stateMutability: "view"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
type: "function",
|
|
243
|
+
name: "approve",
|
|
244
|
+
inputs: [
|
|
245
|
+
{ name: "spender", type: "address" },
|
|
246
|
+
{ name: "amount", type: "uint256" }
|
|
247
|
+
],
|
|
248
|
+
outputs: [{ type: "bool" }],
|
|
249
|
+
stateMutability: "nonpayable"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
type: "function",
|
|
253
|
+
name: "balanceOf",
|
|
254
|
+
inputs: [{ name: "account", type: "address" }],
|
|
255
|
+
outputs: [{ type: "uint256" }],
|
|
256
|
+
stateMutability: "view"
|
|
257
|
+
}
|
|
258
|
+
];
|
|
259
|
+
var permit2Abi = [
|
|
260
|
+
{
|
|
261
|
+
type: "function",
|
|
262
|
+
name: "allowance",
|
|
263
|
+
inputs: [
|
|
264
|
+
{ name: "user", type: "address" },
|
|
265
|
+
{ name: "token", type: "address" },
|
|
266
|
+
{ name: "spender", type: "address" }
|
|
267
|
+
],
|
|
268
|
+
outputs: [
|
|
269
|
+
{ name: "amount", type: "uint160" },
|
|
270
|
+
{ name: "expiration", type: "uint48" },
|
|
271
|
+
{ name: "nonce", type: "uint48" }
|
|
272
|
+
],
|
|
273
|
+
stateMutability: "view"
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
type: "function",
|
|
277
|
+
name: "approve",
|
|
278
|
+
inputs: [
|
|
279
|
+
{ name: "token", type: "address" },
|
|
280
|
+
{ name: "spender", type: "address" },
|
|
281
|
+
{ name: "amount", type: "uint160" },
|
|
282
|
+
{ name: "expiration", type: "uint48" }
|
|
283
|
+
],
|
|
284
|
+
outputs: [],
|
|
285
|
+
stateMutability: "nonpayable"
|
|
286
|
+
}
|
|
287
|
+
];
|
|
288
|
+
var UINT128_MAX = 2n ** 128n - 1n;
|
|
289
|
+
var UINT160_MAX = 2n ** 160n - 1n;
|
|
290
|
+
var UINT48_MAX = 2n ** 48n - 1n;
|
|
291
|
+
function alignPriceToTickBoundary(priceQ96, tickSpacing) {
|
|
292
|
+
if (tickSpacing <= 0n) {
|
|
293
|
+
throw new RangeError(`alignPriceToTickBoundary: tickSpacing must be positive, got ${tickSpacing}`);
|
|
294
|
+
}
|
|
295
|
+
return priceQ96 / tickSpacing * tickSpacing;
|
|
296
|
+
}
|
|
297
|
+
function isPriceOnTickBoundary(priceQ96, tickSpacing) {
|
|
298
|
+
return tickSpacing > 0n && priceQ96 % tickSpacing === 0n;
|
|
299
|
+
}
|
|
300
|
+
function encodeSubmitBid(params) {
|
|
301
|
+
if (params.amount <= 0n) {
|
|
302
|
+
throw new RangeError(`submitBid: amount must be positive, got ${params.amount}`);
|
|
303
|
+
}
|
|
304
|
+
if (params.amount > UINT128_MAX) {
|
|
305
|
+
throw new RangeError(`submitBid: amount ${params.amount} exceeds uint128.`);
|
|
306
|
+
}
|
|
307
|
+
if (params.maxPriceQ96 <= 0n) {
|
|
308
|
+
throw new RangeError(`submitBid: maxPriceQ96 must be positive, got ${params.maxPriceQ96}`);
|
|
309
|
+
}
|
|
310
|
+
if (params.tickSpacing !== void 0 && !isPriceOnTickBoundary(params.maxPriceQ96, params.tickSpacing)) {
|
|
311
|
+
throw new RangeError(
|
|
312
|
+
`submitBid: maxPriceQ96 ${params.maxPriceQ96} is not a multiple of tickSpacing ${params.tickSpacing} and would revert with TickPriceNotAtBoundary(). Use alignPriceToTickBoundary() first.`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const data = params.prevTickPriceQ96 === void 0 ? encodeFunctionData({
|
|
316
|
+
abi: auctionAbi,
|
|
317
|
+
functionName: "submitBid",
|
|
318
|
+
// Four args selects the no-hint overload, which passes FLOOR_PRICE.
|
|
319
|
+
args: [params.maxPriceQ96, params.amount, params.owner, params.hookData ?? "0x"]
|
|
320
|
+
}) : encodeFunctionData({
|
|
321
|
+
abi: auctionAbi,
|
|
322
|
+
functionName: "submitBid",
|
|
323
|
+
args: [
|
|
324
|
+
params.maxPriceQ96,
|
|
325
|
+
params.amount,
|
|
326
|
+
params.owner,
|
|
327
|
+
params.prevTickPriceQ96,
|
|
328
|
+
params.hookData ?? "0x"
|
|
329
|
+
]
|
|
330
|
+
});
|
|
331
|
+
return {
|
|
332
|
+
to: params.auction,
|
|
333
|
+
data,
|
|
334
|
+
// ERC-20 bids are pulled via Permit2; any value sent would revert with
|
|
335
|
+
// CurrencyIsNotNative().
|
|
336
|
+
value: params.isNativeCurrency ? params.amount : 0n
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
async function getPrevTickHint(client, auction, maxPriceQ96) {
|
|
340
|
+
const [floorPrice, nextActive] = await client.publicClient.multicall({
|
|
341
|
+
allowFailure: false,
|
|
342
|
+
contracts: [
|
|
343
|
+
{ address: auction, abi: auctionAbi, functionName: "floorPrice" },
|
|
344
|
+
{ address: auction, abi: auctionAbi, functionName: "nextActiveTickPrice" }
|
|
345
|
+
]
|
|
346
|
+
});
|
|
347
|
+
if (nextActive > floorPrice && nextActive < maxPriceQ96) return nextActive;
|
|
348
|
+
return floorPrice;
|
|
349
|
+
}
|
|
350
|
+
function encodeApproveErc20ToPermit2(currency, amount = maxUint256) {
|
|
351
|
+
return {
|
|
352
|
+
to: currency,
|
|
353
|
+
data: encodeFunctionData({
|
|
354
|
+
abi: erc20ApprovalAbi,
|
|
355
|
+
functionName: "approve",
|
|
356
|
+
args: [PERMIT2_ADDRESS, amount]
|
|
357
|
+
}),
|
|
358
|
+
value: 0n
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function encodeApprovePermit2(currency, auction, amount = UINT160_MAX, expiration) {
|
|
362
|
+
if (amount > UINT160_MAX) {
|
|
363
|
+
throw new RangeError(`Permit2 approve: amount ${amount} exceeds uint160.`);
|
|
364
|
+
}
|
|
365
|
+
const expiry = expiration ?? BigInt(Math.floor(Date.now() / 1e3) + 30 * 24 * 60 * 60);
|
|
366
|
+
if (expiry > UINT48_MAX) {
|
|
367
|
+
throw new RangeError(`Permit2 approve: expiration ${expiry} exceeds uint48.`);
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
to: PERMIT2_ADDRESS,
|
|
371
|
+
data: encodeFunctionData({
|
|
372
|
+
abi: permit2Abi,
|
|
373
|
+
functionName: "approve",
|
|
374
|
+
args: [currency, auction, amount, Number(expiry)]
|
|
375
|
+
}),
|
|
376
|
+
value: 0n
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
async function checkBidReadiness(client, params) {
|
|
380
|
+
if (params.currency === NATIVE_CURRENCY) {
|
|
381
|
+
const balance2 = await client.publicClient.getBalance({ address: params.bidder });
|
|
382
|
+
return {
|
|
383
|
+
hasBalance: balance2 >= params.amount,
|
|
384
|
+
balance: balance2,
|
|
385
|
+
erc20AllowanceToPermit2: maxUint256,
|
|
386
|
+
permit2AllowanceToAuction: maxUint256,
|
|
387
|
+
permit2Expiration: maxUint256,
|
|
388
|
+
needsErc20Approval: false,
|
|
389
|
+
needsPermit2Approval: false,
|
|
390
|
+
needsApproval: false
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const [balance, erc20Allowance, permit2Allowance] = await client.publicClient.multicall({
|
|
394
|
+
allowFailure: false,
|
|
395
|
+
contracts: [
|
|
396
|
+
{
|
|
397
|
+
address: params.currency,
|
|
398
|
+
abi: erc20ApprovalAbi,
|
|
399
|
+
functionName: "balanceOf",
|
|
400
|
+
args: [params.bidder]
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
address: params.currency,
|
|
404
|
+
abi: erc20ApprovalAbi,
|
|
405
|
+
functionName: "allowance",
|
|
406
|
+
args: [params.bidder, PERMIT2_ADDRESS]
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
address: PERMIT2_ADDRESS,
|
|
410
|
+
abi: permit2Abi,
|
|
411
|
+
functionName: "allowance",
|
|
412
|
+
args: [params.bidder, params.currency, params.auction]
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
});
|
|
416
|
+
const [permit2Amount, permit2Expiration] = permit2Allowance;
|
|
417
|
+
const nowSeconds = BigInt(Math.floor(Date.now() / 1e3));
|
|
418
|
+
const permit2Expired = BigInt(permit2Expiration) <= nowSeconds;
|
|
419
|
+
const needsErc20Approval = erc20Allowance < params.amount;
|
|
420
|
+
const needsPermit2Approval = permit2Amount < params.amount || permit2Expired;
|
|
421
|
+
return {
|
|
422
|
+
hasBalance: balance >= params.amount,
|
|
423
|
+
balance,
|
|
424
|
+
erc20AllowanceToPermit2: erc20Allowance,
|
|
425
|
+
permit2AllowanceToAuction: permit2Amount,
|
|
426
|
+
permit2Expiration: BigInt(permit2Expiration),
|
|
427
|
+
needsErc20Approval,
|
|
428
|
+
needsPermit2Approval,
|
|
429
|
+
needsApproval: needsErc20Approval || needsPermit2Approval
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function buildBidTransactions(params) {
|
|
433
|
+
const isNative = params.currency === NATIVE_CURRENCY;
|
|
434
|
+
const steps = [];
|
|
435
|
+
if (!isNative) {
|
|
436
|
+
if (params.readiness.needsErc20Approval) {
|
|
437
|
+
steps.push({
|
|
438
|
+
label: "Approve Permit2 to move the bid currency",
|
|
439
|
+
request: encodeApproveErc20ToPermit2(params.currency)
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
if (params.readiness.needsPermit2Approval) {
|
|
443
|
+
steps.push({
|
|
444
|
+
label: "Grant the auction a Permit2 allowance",
|
|
445
|
+
request: encodeApprovePermit2(params.currency, params.auction)
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const alignedPriceQ96 = params.tickSpacing === void 0 ? params.maxPriceQ96 : alignPriceToTickBoundary(params.maxPriceQ96, params.tickSpacing);
|
|
450
|
+
steps.push({
|
|
451
|
+
label: "Submit bid",
|
|
452
|
+
request: encodeSubmitBid({
|
|
453
|
+
auction: params.auction,
|
|
454
|
+
maxPriceQ96: alignedPriceQ96,
|
|
455
|
+
amount: params.amount,
|
|
456
|
+
owner: params.owner,
|
|
457
|
+
isNativeCurrency: isNative,
|
|
458
|
+
prevTickPriceQ96: params.prevTickPriceQ96,
|
|
459
|
+
hookData: params.hookData,
|
|
460
|
+
tickSpacing: params.tickSpacing
|
|
461
|
+
})
|
|
462
|
+
});
|
|
463
|
+
return steps;
|
|
464
|
+
}
|
|
465
|
+
function describeBidBlocker(input) {
|
|
466
|
+
if (input.status === "ended") return "This auction has ended - bidding is closed.";
|
|
467
|
+
if (input.status === "pending") return "This auction has not opened yet.";
|
|
468
|
+
if (input.amount <= 0n) return "Enter a bid amount.";
|
|
469
|
+
if (input.maxPriceQ96 <= 0n) return "Enter a maximum price.";
|
|
470
|
+
if (input.maxPriceQ96 < input.floorPriceQ96) {
|
|
471
|
+
return "Your maximum price is below the auction's floor price.";
|
|
472
|
+
}
|
|
473
|
+
if (input.tickSpacing !== void 0) {
|
|
474
|
+
const aligned = alignPriceToTickBoundary(input.maxPriceQ96, input.tickSpacing);
|
|
475
|
+
if (aligned < input.floorPriceQ96) {
|
|
476
|
+
return "Your maximum price rounds down below the auction's floor price.";
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (input.readiness && !input.readiness.hasBalance) return "Insufficient balance.";
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export { AuctionParametersError, MIN_FLOOR_PRICE, MIN_TICK_SPACING, PrivateKeyFormatError, alignPriceToTickBoundary, assertChainMatches, assertSupportedChain, buildAuctionSteps, buildBidTransactions, checkBidReadiness, createBeaconClient, decodeAuctionParameters, describeBidBlocker, encodeApproveErc20ToPermit2, encodeApprovePermit2, encodeAuctionParameters, encodeSubmitBid, erc20ApprovalAbi, getPrevTickHint, isPriceOnTickBoundary, isValidPrivateKey, normalizePrivateKey, packAuctionSteps, permit2Abi, totalBlocks, totalMps, unpackAuctionSteps, validateAuctionParameters };
|
|
484
|
+
//# sourceMappingURL=index.js.map
|
|
485
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auctionParameters.ts","../src/client.ts","../src/privateKey.ts","../src/write.ts"],"names":["balance"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Encoding and validating `AuctionParameters`, the blob the factory takes as\n * `configData`.\n *\n * Every rule enforced here is transcribed from the v1.1.0 constructors\n * (`StepStorage`, `TickStorage`, `ContinuousClearingAuction`) rather than\n * guessed, and each check names the revert it prevents. Getting any of them\n * wrong means a deployment that reverts after you have already spent gas\n * minting and transferring a token supply.\n */\nimport { decodeAbiParameters, encodeAbiParameters, parseAbiParameters } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport { MPS } from \"./q96\";\n\n/** Mirrors `struct AuctionParameters`. */\nexport interface AuctionParameters {\n /** Currency to raise in. `address(0)` for native ETH. */\n currency: Address;\n /** Receives leftover unsold tokens. */\n tokensRecipient: Address;\n /** Receives the raised funds. */\n fundsRecipient: Address;\n startBlock: bigint;\n endBlock: bigint;\n /** Block from which bidders can claim. Must be >= endBlock. */\n claimBlock: bigint;\n tickSpacing: bigint;\n /** Optional pre-bid hook. `address(0)` for none. */\n validationHook: Address;\n /** Q96-encoded starting price. */\n floorPriceQ96: bigint;\n /** Currency that must be raised for the auction to graduate. */\n requiredCurrencyRaised: bigint;\n /** Packed supply schedule. Build with {@link buildAuctionSteps}. */\n auctionStepsData: Hex;\n}\n\n/** One entry of the supply schedule. */\nexport interface AuctionStep {\n /** Milli-bips of supply released per block during this step. uint24. */\n mps: number;\n /** How many blocks this step lasts. uint40, must be > 0. */\n blockDelta: number;\n}\n\n/** `ConstantsLib.MIN_FLOOR_PRICE` - `type(uint32).max + 1`. */\nexport const MIN_FLOOR_PRICE = 4_294_967_296n;\n/** `ConstantsLib.MIN_TICK_SPACING`. */\nexport const MIN_TICK_SPACING = 2n;\n\nconst UINT24_MAX = 16_777_215;\nconst UINT40_MAX = 1_099_511_627_775;\n\nexport class AuctionParametersError extends Error {}\n\n/**\n * Pack steps into `auctionStepsData`.\n *\n * Each step is one 8-byte word: `uint24 mps | uint40 blockDelta`.\n */\nexport function packAuctionSteps(steps: readonly AuctionStep[]): Hex {\n if (steps.length === 0) {\n throw new AuctionParametersError(\"auctionStepsData cannot be empty (InvalidAuctionDataLength).\");\n }\n\n let packed = \"\";\n for (const step of steps) {\n if (!Number.isInteger(step.mps) || step.mps < 0 || step.mps > UINT24_MAX) {\n throw new AuctionParametersError(`step mps ${step.mps} does not fit uint24.`);\n }\n if (!Number.isInteger(step.blockDelta) || step.blockDelta <= 0) {\n // StepBlockDeltaCannotBeZero()\n throw new AuctionParametersError(`step blockDelta must be > 0, got ${step.blockDelta}.`);\n }\n if (step.blockDelta > UINT40_MAX) {\n throw new AuctionParametersError(`step blockDelta ${step.blockDelta} does not fit uint40.`);\n }\n packed += step.mps.toString(16).padStart(6, \"0\");\n packed += step.blockDelta.toString(16).padStart(10, \"0\");\n }\n\n return `0x${packed}`;\n}\n\n/** Unpack `auctionStepsData` back into steps. */\nexport function unpackAuctionSteps(data: Hex): AuctionStep[] {\n const body = data.slice(2);\n if (body.length === 0 || body.length % 16 !== 0) {\n throw new AuctionParametersError(\n `auctionStepsData must be a whole number of 8-byte steps, got ${body.length / 2} bytes.`,\n );\n }\n\n const steps: AuctionStep[] = [];\n for (let i = 0; i < body.length; i += 16) {\n steps.push({\n mps: Number.parseInt(body.slice(i, i + 6), 16),\n blockDelta: Number.parseInt(body.slice(i + 6, i + 16), 16),\n });\n }\n return steps;\n}\n\n/**\n * Build a schedule that releases the whole supply evenly over `durationBlocks`.\n *\n * The contract requires `sum(mps * blockDelta) == MPS` exactly. `MPS` (1e7)\n * rarely divides evenly by a duration, so this emits a flat step for all but\n * the last block and puts the remainder on the final block, rather than\n * silently rounding and reverting with `InvalidStepDataMps`.\n */\nexport function buildAuctionSteps(durationBlocks: number): AuctionStep[] {\n if (!Number.isInteger(durationBlocks) || durationBlocks < 2) {\n throw new AuctionParametersError(`durationBlocks must be an integer >= 2, got ${durationBlocks}.`);\n }\n\n const total = Number(MPS);\n const perBlock = Math.floor(total / durationBlocks);\n if (perBlock <= 0) {\n throw new AuctionParametersError(\n `durationBlocks ${durationBlocks} is longer than MPS (${total}) allows - each block would release 0.`,\n );\n }\n\n const remainder = total - perBlock * durationBlocks;\n const finalBlockMps = perBlock + remainder;\n\n if (finalBlockMps > UINT24_MAX) {\n throw new AuctionParametersError(`final step mps ${finalBlockMps} does not fit uint24.`);\n }\n\n return durationBlocks === 1\n ? [{ mps: total, blockDelta: 1 }]\n : [\n { mps: perBlock, blockDelta: durationBlocks - 1 },\n { mps: finalBlockMps, blockDelta: 1 },\n ];\n}\n\n/** Total supply released by a schedule, which must equal `MPS`. */\nexport function totalMps(steps: readonly AuctionStep[]): bigint {\n return steps.reduce((sum, step) => sum + BigInt(step.mps) * BigInt(step.blockDelta), 0n);\n}\n\n/** Total blocks a schedule spans. */\nexport function totalBlocks(steps: readonly AuctionStep[]): bigint {\n return steps.reduce((sum, step) => sum + BigInt(step.blockDelta), 0n);\n}\n\n/**\n * Validate parameters against every constructor rule, before spending gas.\n * Throws on the first violation, naming the revert it would have caused.\n */\nexport function validateAuctionParameters(params: AuctionParameters): void {\n // StepStorage: InvalidEndBlock()\n if (params.startBlock >= params.endBlock) {\n throw new AuctionParametersError(\n `startBlock (${params.startBlock}) must be < endBlock (${params.endBlock}) - InvalidEndBlock.`,\n );\n }\n\n // ContinuousClearingAuction: ClaimBlockIsBeforeEndBlock()\n if (params.claimBlock < params.endBlock) {\n throw new AuctionParametersError(\n `claimBlock (${params.claimBlock}) must be >= endBlock (${params.endBlock}) - ClaimBlockIsBeforeEndBlock.`,\n );\n }\n\n // TickStorage: TickSpacingTooSmall()\n if (params.tickSpacing < MIN_TICK_SPACING) {\n throw new AuctionParametersError(\n `tickSpacing (${params.tickSpacing}) must be >= ${MIN_TICK_SPACING} - TickSpacingTooSmall.`,\n );\n }\n\n // TickStorage: FloorPriceIsZero() / FloorPriceTooLow()\n if (params.floorPriceQ96 === 0n) {\n throw new AuctionParametersError(\"floorPrice cannot be zero - FloorPriceIsZero.\");\n }\n if (params.floorPriceQ96 < MIN_FLOOR_PRICE) {\n throw new AuctionParametersError(\n `floorPrice (${params.floorPriceQ96}) must be >= ${MIN_FLOOR_PRICE} - FloorPriceTooLow.`,\n );\n }\n\n // TickStorage._getTick: TickPriceNotAtBoundary()\n if (params.floorPriceQ96 % params.tickSpacing !== 0n) {\n throw new AuctionParametersError(\n `floorPrice (${params.floorPriceQ96}) must be a multiple of tickSpacing (${params.tickSpacing}) - TickPriceNotAtBoundary.`,\n );\n }\n\n // StepStorage._validate\n const steps = unpackAuctionSteps(params.auctionStepsData);\n const mpsSum = totalMps(steps);\n if (mpsSum !== MPS) {\n throw new AuctionParametersError(\n `supply schedule releases ${mpsSum} mps, must be exactly ${MPS} - InvalidStepDataMps.`,\n );\n }\n\n const blockSum = totalBlocks(steps);\n if (params.startBlock + blockSum !== params.endBlock) {\n throw new AuctionParametersError(\n `supply schedule spans ${blockSum} blocks, so it ends at ${params.startBlock + blockSum}, ` +\n `but endBlock is ${params.endBlock} - InvalidEndBlockGivenStepData.`,\n );\n }\n}\n\n/** ABI tuple matching `AuctionParameters`, in declaration order. */\nconst PARAMETERS_TUPLE = parseAbiParameters(\n \"(address currency, address tokensRecipient, address fundsRecipient, uint64 startBlock, uint64 endBlock, uint64 claimBlock, uint256 tickSpacing, address validationHook, uint256 floorPrice, uint128 requiredCurrencyRaised, bytes auctionStepsData)\",\n);\n\n/**\n * ABI-encode parameters into the factory's `configData`.\n * Validates first - the factory decodes this blindly and the auction's\n * constructor is where a bad value surfaces, after the gas is spent.\n */\nexport function encodeAuctionParameters(params: AuctionParameters): Hex {\n validateAuctionParameters(params);\n\n return encodeAbiParameters(PARAMETERS_TUPLE, [\n {\n currency: params.currency,\n tokensRecipient: params.tokensRecipient,\n fundsRecipient: params.fundsRecipient,\n startBlock: params.startBlock,\n endBlock: params.endBlock,\n claimBlock: params.claimBlock,\n tickSpacing: params.tickSpacing,\n validationHook: params.validationHook,\n floorPrice: params.floorPriceQ96,\n requiredCurrencyRaised: params.requiredCurrencyRaised,\n auctionStepsData: params.auctionStepsData,\n },\n ]);\n}\n\n/**\n * Decode a factory `AuctionCreated` event's `configData`.\n * Lets the directory show how any auction was configured.\n */\nexport function decodeAuctionParameters(configData: Hex): AuctionParameters {\n const [decoded] = decodeAbiParameters(PARAMETERS_TUPLE, configData);\n const value = decoded as unknown as {\n currency: Address;\n tokensRecipient: Address;\n fundsRecipient: Address;\n startBlock: bigint;\n endBlock: bigint;\n claimBlock: bigint;\n tickSpacing: bigint;\n validationHook: Address;\n floorPrice: bigint;\n requiredCurrencyRaised: bigint;\n auctionStepsData: Hex;\n };\n\n return {\n currency: value.currency,\n tokensRecipient: value.tokensRecipient,\n fundsRecipient: value.fundsRecipient,\n startBlock: BigInt(value.startBlock),\n endBlock: BigInt(value.endBlock),\n claimBlock: BigInt(value.claimBlock),\n tickSpacing: value.tickSpacing,\n validationHook: value.validationHook,\n floorPriceQ96: value.floorPrice,\n requiredCurrencyRaised: value.requiredCurrencyRaised,\n auctionStepsData: value.auctionStepsData,\n };\n}\n","/**\r\n * Beacon's client wrapper: a viem public client plus the addresses every read\r\n * needs, with a hard testnet guard.\r\n */\r\nimport { createPublicClient, http } from \"viem\";\r\nimport type { Address, Chain, PublicClient } from \"viem\";\r\nimport {\r\n BLOCK_TIME_SECONDS,\r\n FACTORY_ADDRESS,\r\n FACTORY_DEPLOY_BLOCK,\r\n SUPPORTED_CHAINS,\r\n} from \"./constants\";\r\n\r\nexport interface BeaconClientConfig {\r\n /** JSON-RPC endpoint used for state reads (eth_call, multicall). */\r\n rpcUrl: string;\r\n /**\r\n * Optional separate endpoint for `eth_getLogs`.\r\n *\r\n * State reads and log scanning have different provider requirements, and one\r\n * endpoint is often bad at one of them. Alchemy's free tier serves eth_call\r\n * beautifully but caps eth_getLogs at a 10-block range, which makes a\r\n * historical backfill impossible; public wide-range endpoints allow the range\r\n * but can silently return empty results. Defaults to `rpcUrl`.\r\n */\r\n logsRpcUrl?: string;\r\n /** Chain id. Must be a supported testnet - mainnet is rejected. */\r\n chainId: number;\r\n /** Override the factory address. Defaults to the cross-chain singleton. */\r\n factoryAddress?: Address;\r\n /**\r\n * Address of a deployed `AuctionStateLens`.\r\n *\r\n * Optional. When omitted, reads run the lens via an `eth_call` state\r\n * override instead, so no deployment is required. See `read.ts`.\r\n */\r\n lensAddress?: Address;\r\n /** Override the block the factory was deployed at. */\r\n factoryDeployBlock?: bigint;\r\n}\r\n\r\nexport interface BeaconClient {\r\n publicClient: PublicClient;\r\n /** Client used for `eth_getLogs`. Same as `publicClient` unless overridden. */\r\n logsClient: PublicClient;\r\n chain: Chain;\r\n chainId: number;\r\n factoryAddress: Address;\r\n lensAddress?: Address;\r\n factoryDeployBlock: bigint;\r\n /** Approximate seconds per block, for time-remaining estimates. */\r\n blockTimeSeconds: number;\r\n}\r\n\r\n/**\r\n * Throw unless `chainId` is a testnet Beacon supports.\r\n *\r\n * This is the choke point that keeps every Beacon surface off mainnet. The\r\n * bidding agent layers its own guard on top before it broadcasts anything.\r\n */\r\nexport function assertSupportedChain(chainId: number): Chain {\r\n const chain = SUPPORTED_CHAINS[chainId as keyof typeof SUPPORTED_CHAINS];\r\n if (!chain) {\r\n const supported = Object.values(SUPPORTED_CHAINS)\r\n .map((c) => `${c.name} (${c.id})`)\r\n .join(\", \");\r\n throw new Error(\r\n `Beacon is testnet-only and refuses to run against chain id ${chainId}. Supported: ${supported}.`,\r\n );\r\n }\r\n return chain;\r\n}\r\n\r\n/** Build a Beacon client. Throws immediately on a non-testnet chain id. */\r\nexport function createBeaconClient(config: BeaconClientConfig): BeaconClient {\r\n const chain = assertSupportedChain(config.chainId);\r\n\r\n const publicClient = createPublicClient({\r\n chain,\r\n transport: http(config.rpcUrl),\r\n // Batch eagerly: reads fan out across many auctions in the directory.\r\n batch: { multicall: true },\r\n }) as PublicClient;\r\n\r\n const logsClient =\r\n config.logsRpcUrl && config.logsRpcUrl !== config.rpcUrl\r\n ? (createPublicClient({ chain, transport: http(config.logsRpcUrl) }) as PublicClient)\r\n : publicClient;\r\n\r\n const factoryDeployBlock = config.factoryDeployBlock ?? FACTORY_DEPLOY_BLOCK[config.chainId];\r\n if (factoryDeployBlock === undefined) {\r\n throw new Error(\r\n `No known factory deploy block for chain id ${config.chainId}. ` +\r\n `Pass \\`factoryDeployBlock\\` explicitly so discovery knows where to start scanning.`,\r\n );\r\n }\r\n\r\n return {\r\n publicClient,\r\n logsClient,\r\n chain,\r\n chainId: config.chainId,\r\n factoryAddress: config.factoryAddress ?? FACTORY_ADDRESS,\r\n lensAddress: config.lensAddress,\r\n factoryDeployBlock,\r\n blockTimeSeconds: BLOCK_TIME_SECONDS[config.chainId] ?? 12,\r\n };\r\n}\r\n\r\n/**\r\n * Confirm the RPC is actually serving the chain we were configured for.\r\n *\r\n * Cheap insurance against a copy-pasted RPC URL silently pointing at the wrong\r\n * network - which otherwise shows up as \"no auctions found\".\r\n */\r\nexport async function assertChainMatches(client: BeaconClient): Promise<void> {\r\n const actual = await client.publicClient.getChainId();\r\n if (actual !== client.chainId) {\r\n throw new Error(\r\n `RPC chain id mismatch: configured for ${client.chainId} (${client.chain.name}) ` +\r\n `but the endpoint reports ${actual}. Check BEACON_RPC_URL.`,\r\n );\r\n }\r\n}\r\n","/**\n * Private-key format normalisation.\n *\n * This module does exactly one thing: turn a key string into the `0x`-prefixed\n * form viem requires, or throw. It never logs, never stores, and never returns\n * anything derived from the key other than the key itself.\n *\n * It exists because wallets and key exporters disagree about the `0x` prefix -\n * Foundry's `cast wallet new` prints one, several UIs do not - and bouncing a\n * user back to edit their `.env` over two characters is a poor trade for the\n * risk of them pasting the key somewhere less safe while they fix it.\n */\n\n/** A 32-byte private key in the form viem expects. */\nexport type PrivateKeyHex = `0x${string}`;\n\nexport class PrivateKeyFormatError extends Error {}\n\n/**\n * Normalise a private key to `0x` + 64 hex characters.\n *\n * Accepts the key with or without the prefix. Every error message here is\n * deliberately written to describe the SHAPE of the problem and never to echo\n * any part of the value.\n *\n * @throws PrivateKeyFormatError when the value is not a 32-byte hex key.\n */\nexport function normalizePrivateKey(raw: string | undefined, envVarName: string): PrivateKeyHex {\n const trimmed = (raw ?? \"\").trim();\n\n if (trimmed.length === 0) {\n throw new PrivateKeyFormatError(`${envVarName} is not set.`);\n }\n\n const body = trimmed.startsWith(\"0x\") || trimmed.startsWith(\"0X\") ? trimmed.slice(2) : trimmed;\n\n if (!/^[0-9a-fA-F]{64}$/.test(body)) {\n throw new PrivateKeyFormatError(\n `${envVarName} is malformed: expected 64 hex characters (optionally 0x-prefixed), ` +\n `got ${body.length} characters. The value itself is not shown.`,\n );\n }\n\n if (/^0{64}$/.test(body)) {\n throw new PrivateKeyFormatError(\n `${envVarName} is all zeros - that is the .env.example placeholder, not a real key.`,\n );\n }\n\n return `0x${body.toLowerCase()}` as PrivateKeyHex;\n}\n\n/** True when a key is present and well-formed, without throwing. */\nexport function isValidPrivateKey(raw: string | undefined): boolean {\n try {\n normalizePrivateKey(raw, \"key\");\n return true;\n } catch {\n return false;\n }\n}\n","/**\r\n * The write path: building a `submitBid` transaction.\r\n *\r\n * Three things here are easy to get wrong, and all three are verified against\r\n * the v1.1.0 contract source rather than assumed:\r\n *\r\n * 1. ERC-20 bids go through PERMIT2, not a plain `transferFrom`.\r\n * `ContinuousClearingAuction.submitBid` calls\r\n * `SafeTransferLib.permit2TransferFrom(...)` unconditionally for a\r\n * non-native currency, which invokes Permit2's\r\n * `transferFrom(from,to,uint160,token)`. Approving the AUCTION directly does\r\n * nothing - the auction never calls `token.transferFrom`. A bidder needs\r\n * BOTH an ERC-20 approval to Permit2 AND a Permit2 allowance for the\r\n * auction as spender.\r\n *\r\n * 2. Native bids must send `msg.value == amount` exactly, and must send zero\r\n * value for ERC-20 currencies (`if (msg.value != 0) revert\r\n * CurrencyIsNotNative()`).\r\n *\r\n * 3. `prevTickPrice` is a search hint, not a price the bidder pays. The\r\n * four-argument overload is literally\r\n * `submitBid(maxPrice, amount, owner, FLOOR_PRICE, hookData)` - always\r\n * correct, but it walks every tick from the floor.\r\n *\r\n * Nothing here broadcasts. These helpers return request objects for a caller's\r\n * wallet client to send, which keeps signing concerns out of the SDK.\r\n */\r\nimport { encodeFunctionData, maxUint256 } from \"viem\";\r\nimport type { Address, Hex } from \"viem\";\r\nimport { auctionAbi } from \"./abis\";\r\nimport type { BeaconClient } from \"./client\";\r\nimport { NATIVE_CURRENCY, PERMIT2_ADDRESS } from \"./constants\";\r\nimport { erc20MetadataAbi } from \"./erc20\";\r\n\r\n/** ERC-20 approval + balance surface. */\r\nexport const erc20ApprovalAbi = [\r\n ...erc20MetadataAbi,\r\n {\r\n type: \"function\",\r\n name: \"allowance\",\r\n inputs: [\r\n { name: \"owner\", type: \"address\" },\r\n { name: \"spender\", type: \"address\" },\r\n ],\r\n outputs: [{ type: \"uint256\" }],\r\n stateMutability: \"view\",\r\n },\r\n {\r\n type: \"function\",\r\n name: \"approve\",\r\n inputs: [\r\n { name: \"spender\", type: \"address\" },\r\n { name: \"amount\", type: \"uint256\" },\r\n ],\r\n outputs: [{ type: \"bool\" }],\r\n stateMutability: \"nonpayable\",\r\n },\r\n {\r\n type: \"function\",\r\n name: \"balanceOf\",\r\n inputs: [{ name: \"account\", type: \"address\" }],\r\n outputs: [{ type: \"uint256\" }],\r\n stateMutability: \"view\",\r\n },\r\n] as const;\r\n\r\n/** Permit2 AllowanceTransfer, the second half of the ERC-20 approval flow. */\r\nexport const permit2Abi = [\r\n {\r\n type: \"function\",\r\n name: \"allowance\",\r\n inputs: [\r\n { name: \"user\", type: \"address\" },\r\n { name: \"token\", type: \"address\" },\r\n { name: \"spender\", type: \"address\" },\r\n ],\r\n outputs: [\r\n { name: \"amount\", type: \"uint160\" },\r\n { name: \"expiration\", type: \"uint48\" },\r\n { name: \"nonce\", type: \"uint48\" },\r\n ],\r\n stateMutability: \"view\",\r\n },\r\n {\r\n type: \"function\",\r\n name: \"approve\",\r\n inputs: [\r\n { name: \"token\", type: \"address\" },\r\n { name: \"spender\", type: \"address\" },\r\n { name: \"amount\", type: \"uint160\" },\r\n { name: \"expiration\", type: \"uint48\" },\r\n ],\r\n outputs: [],\r\n stateMutability: \"nonpayable\",\r\n },\r\n] as const;\r\n\r\nconst UINT128_MAX = 2n ** 128n - 1n;\r\nconst UINT160_MAX = 2n ** 160n - 1n;\r\nconst UINT48_MAX = 2n ** 48n - 1n;\r\n\r\nexport interface TransactionRequest {\r\n to: Address;\r\n data: Hex;\r\n value: bigint;\r\n}\r\n\r\n/**\r\n * Round a price DOWN to the nearest tick boundary.\r\n *\r\n * `submitBid` reverts with `TickPriceNotAtBoundary()` unless `maxPrice` is an\r\n * exact multiple of the auction's `tickSpacing` - the same rule the constructor\r\n * applies to the floor price, but it also applies to every bid. A price typed\r\n * by a human almost never lands on a boundary, so it has to be snapped.\r\n *\r\n * Rounding DOWN is the only safe direction: it can only lower the ceiling a\r\n * bidder is committing to, never raise it above what they asked for.\r\n */\r\nexport function alignPriceToTickBoundary(priceQ96: bigint, tickSpacing: bigint): bigint {\r\n if (tickSpacing <= 0n) {\r\n throw new RangeError(`alignPriceToTickBoundary: tickSpacing must be positive, got ${tickSpacing}`);\r\n }\r\n return (priceQ96 / tickSpacing) * tickSpacing;\r\n}\r\n\r\n/** True when a price sits exactly on a tick boundary. */\r\nexport function isPriceOnTickBoundary(priceQ96: bigint, tickSpacing: bigint): boolean {\r\n return tickSpacing > 0n && priceQ96 % tickSpacing === 0n;\r\n}\r\n\r\nexport interface SubmitBidParams {\r\n auction: Address;\r\n /** Highest price the bidder will pay, Q96-encoded. Use `decimalToQ96`. */\r\n maxPriceQ96: bigint;\r\n /** Bid size, in the currency's smallest unit. uint128 on-chain. */\r\n amount: bigint;\r\n /** Who owns the resulting bid. */\r\n owner: Address;\r\n /** Extra data for a validation hook, if the auction has one. */\r\n hookData?: Hex;\r\n /** True when the auction's currency is native ETH, so `value` must be sent. */\r\n isNativeCurrency: boolean;\r\n /**\r\n * Search hint: the initialised tick at or below `maxPriceQ96`. Omit to use\r\n * the floor price, which is always correct but costs more gas.\r\n * See {@link getPrevTickHint}.\r\n */\r\n prevTickPriceQ96?: bigint;\r\n /**\r\n * The auction's tick spacing. When given, the price is checked against it so\r\n * a misaligned bid fails here with a clear message instead of on-chain with\r\n * an opaque `TickPriceNotAtBoundary()`.\r\n */\r\n tickSpacing?: bigint;\r\n}\r\n\r\n/**\r\n * Encode a `submitBid` call.\r\n *\r\n * @throws RangeError if the amount does not fit the contract's `uint128`.\r\n */\r\nexport function encodeSubmitBid(params: SubmitBidParams): TransactionRequest {\r\n if (params.amount <= 0n) {\r\n throw new RangeError(`submitBid: amount must be positive, got ${params.amount}`);\r\n }\r\n if (params.amount > UINT128_MAX) {\r\n throw new RangeError(`submitBid: amount ${params.amount} exceeds uint128.`);\r\n }\r\n if (params.maxPriceQ96 <= 0n) {\r\n throw new RangeError(`submitBid: maxPriceQ96 must be positive, got ${params.maxPriceQ96}`);\r\n }\r\n if (params.tickSpacing !== undefined && !isPriceOnTickBoundary(params.maxPriceQ96, params.tickSpacing)) {\r\n throw new RangeError(\r\n `submitBid: maxPriceQ96 ${params.maxPriceQ96} is not a multiple of tickSpacing ` +\r\n `${params.tickSpacing} and would revert with TickPriceNotAtBoundary(). ` +\r\n `Use alignPriceToTickBoundary() first.`,\r\n );\r\n }\r\n\r\n const data =\r\n params.prevTickPriceQ96 === undefined\r\n ? encodeFunctionData({\r\n abi: auctionAbi,\r\n functionName: \"submitBid\",\r\n // Four args selects the no-hint overload, which passes FLOOR_PRICE.\r\n args: [params.maxPriceQ96, params.amount, params.owner, params.hookData ?? \"0x\"],\r\n })\r\n : encodeFunctionData({\r\n abi: auctionAbi,\r\n functionName: \"submitBid\",\r\n args: [\r\n params.maxPriceQ96,\r\n params.amount,\r\n params.owner,\r\n params.prevTickPriceQ96,\r\n params.hookData ?? \"0x\",\r\n ],\r\n });\r\n\r\n return {\r\n to: params.auction,\r\n data,\r\n // ERC-20 bids are pulled via Permit2; any value sent would revert with\r\n // CurrencyIsNotNative().\r\n value: params.isNativeCurrency ? params.amount : 0n,\r\n };\r\n}\r\n\r\n/**\r\n * Look up a `prevTickPrice` hint.\r\n *\r\n * The auction inserts a bid by walking initialised ticks upward from this\r\n * price. `nextActiveTickPrice()` is the next initialised tick above the current\r\n * clearing price, so it is a strictly better starting point than the floor\r\n * whenever the bid sits above it. Anything at or above `maxPrice` would make\r\n * the contract search past the target, so those fall back to the floor.\r\n *\r\n * A wrong hint cannot make a bid execute at a different price - it only affects\r\n * how far the contract searches - but a hint above the target reverts, so this\r\n * is deliberately conservative.\r\n */\r\nexport async function getPrevTickHint(\r\n client: BeaconClient,\r\n auction: Address,\r\n maxPriceQ96: bigint,\r\n): Promise<bigint> {\r\n const [floorPrice, nextActive] = await client.publicClient.multicall({\r\n allowFailure: false,\r\n contracts: [\r\n { address: auction, abi: auctionAbi, functionName: \"floorPrice\" },\r\n { address: auction, abi: auctionAbi, functionName: \"nextActiveTickPrice\" },\r\n ],\r\n });\r\n\r\n if (nextActive > floorPrice && nextActive < maxPriceQ96) return nextActive;\r\n return floorPrice;\r\n}\r\n\r\n/** Approve Permit2 to move the bid currency. Step 1 of 2 for ERC-20 bids. */\r\nexport function encodeApproveErc20ToPermit2(\r\n currency: Address,\r\n amount: bigint = maxUint256,\r\n): TransactionRequest {\r\n return {\r\n to: currency,\r\n data: encodeFunctionData({\r\n abi: erc20ApprovalAbi,\r\n functionName: \"approve\",\r\n args: [PERMIT2_ADDRESS, amount],\r\n }),\r\n value: 0n,\r\n };\r\n}\r\n\r\n/**\r\n * Grant the auction a Permit2 allowance. Step 2 of 2 for ERC-20 bids.\r\n *\r\n * @param expiration Unix seconds. Defaults to 30 days out; Permit2 stores this\r\n * as a uint48, and an expired allowance fails at transfer time.\r\n */\r\nexport function encodeApprovePermit2(\r\n currency: Address,\r\n auction: Address,\r\n amount: bigint = UINT160_MAX,\r\n expiration?: bigint,\r\n): TransactionRequest {\r\n if (amount > UINT160_MAX) {\r\n throw new RangeError(`Permit2 approve: amount ${amount} exceeds uint160.`);\r\n }\r\n const expiry = expiration ?? BigInt(Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60);\r\n if (expiry > UINT48_MAX) {\r\n throw new RangeError(`Permit2 approve: expiration ${expiry} exceeds uint48.`);\r\n }\r\n\r\n return {\r\n to: PERMIT2_ADDRESS,\r\n data: encodeFunctionData({\r\n abi: permit2Abi,\r\n functionName: \"approve\",\r\n args: [currency, auction, amount, Number(expiry)],\r\n }),\r\n value: 0n,\r\n };\r\n}\r\n\r\nexport interface BidReadiness {\r\n /** Whether the bidder holds enough of the currency. */\r\n hasBalance: boolean;\r\n balance: bigint;\r\n /** ERC-20 allowance granted to Permit2. Unlimited for native currency. */\r\n erc20AllowanceToPermit2: bigint;\r\n /** Permit2 allowance granted to the auction. Unlimited for native currency. */\r\n permit2AllowanceToAuction: bigint;\r\n /** When the Permit2 allowance lapses, as unix seconds. */\r\n permit2Expiration: bigint;\r\n /** True when step 1 (ERC-20 -> Permit2) is still needed. */\r\n needsErc20Approval: boolean;\r\n /** True when step 2 (Permit2 -> auction) is still needed. */\r\n needsPermit2Approval: boolean;\r\n /** Convenience: either approval step is outstanding. */\r\n needsApproval: boolean;\r\n}\r\n\r\n/**\r\n * Check whether a bidder can actually submit a given bid right now.\r\n *\r\n * Native-currency auctions need no approvals at all, so those are reported as\r\n * unlimited rather than pretending an allowance was read.\r\n */\r\nexport async function checkBidReadiness(\r\n client: BeaconClient,\r\n params: { auction: Address; currency: Address; bidder: Address; amount: bigint },\r\n): Promise<BidReadiness> {\r\n if (params.currency === NATIVE_CURRENCY) {\r\n const balance = await client.publicClient.getBalance({ address: params.bidder });\r\n return {\r\n hasBalance: balance >= params.amount,\r\n balance,\r\n erc20AllowanceToPermit2: maxUint256,\r\n permit2AllowanceToAuction: maxUint256,\r\n permit2Expiration: maxUint256,\r\n needsErc20Approval: false,\r\n needsPermit2Approval: false,\r\n needsApproval: false,\r\n };\r\n }\r\n\r\n const [balance, erc20Allowance, permit2Allowance] = await client.publicClient.multicall({\r\n allowFailure: false,\r\n contracts: [\r\n {\r\n address: params.currency,\r\n abi: erc20ApprovalAbi,\r\n functionName: \"balanceOf\",\r\n args: [params.bidder],\r\n },\r\n {\r\n address: params.currency,\r\n abi: erc20ApprovalAbi,\r\n functionName: \"allowance\",\r\n args: [params.bidder, PERMIT2_ADDRESS],\r\n },\r\n {\r\n address: PERMIT2_ADDRESS,\r\n abi: permit2Abi,\r\n functionName: \"allowance\",\r\n args: [params.bidder, params.currency, params.auction],\r\n },\r\n ],\r\n });\r\n\r\n const [permit2Amount, permit2Expiration] = permit2Allowance as readonly [bigint, number, number];\r\n const nowSeconds = BigInt(Math.floor(Date.now() / 1000));\r\n const permit2Expired = BigInt(permit2Expiration) <= nowSeconds;\r\n\r\n const needsErc20Approval = erc20Allowance < params.amount;\r\n const needsPermit2Approval = permit2Amount < params.amount || permit2Expired;\r\n\r\n return {\r\n hasBalance: balance >= params.amount,\r\n balance,\r\n erc20AllowanceToPermit2: erc20Allowance,\r\n permit2AllowanceToAuction: permit2Amount,\r\n permit2Expiration: BigInt(permit2Expiration),\r\n needsErc20Approval,\r\n needsPermit2Approval,\r\n needsApproval: needsErc20Approval || needsPermit2Approval,\r\n };\r\n}\r\n\r\n/**\r\n * Every transaction needed to place this bid, in order.\r\n * One entry for a native bid; up to three for an ERC-20 bid.\r\n */\r\nexport function buildBidTransactions(params: {\r\n auction: Address;\r\n currency: Address;\r\n maxPriceQ96: bigint;\r\n amount: bigint;\r\n owner: Address;\r\n readiness: BidReadiness;\r\n prevTickPriceQ96?: bigint;\r\n hookData?: Hex;\r\n /** When given, the price is snapped down to a boundary before encoding. */\r\n tickSpacing?: bigint;\r\n}): Array<{ label: string; request: TransactionRequest }> {\r\n const isNative = params.currency === NATIVE_CURRENCY;\r\n const steps: Array<{ label: string; request: TransactionRequest }> = [];\r\n\r\n if (!isNative) {\r\n if (params.readiness.needsErc20Approval) {\r\n steps.push({\r\n label: \"Approve Permit2 to move the bid currency\",\r\n request: encodeApproveErc20ToPermit2(params.currency),\r\n });\r\n }\r\n if (params.readiness.needsPermit2Approval) {\r\n steps.push({\r\n label: \"Grant the auction a Permit2 allowance\",\r\n request: encodeApprovePermit2(params.currency, params.auction),\r\n });\r\n }\r\n }\r\n\r\n // A human-entered price almost never lands on a tick boundary, and the\r\n // contract rejects it outright. Snap down before encoding.\r\n const alignedPriceQ96 =\r\n params.tickSpacing === undefined\r\n ? params.maxPriceQ96\r\n : alignPriceToTickBoundary(params.maxPriceQ96, params.tickSpacing);\r\n\r\n steps.push({\r\n label: \"Submit bid\",\r\n request: encodeSubmitBid({\r\n auction: params.auction,\r\n maxPriceQ96: alignedPriceQ96,\r\n amount: params.amount,\r\n owner: params.owner,\r\n isNativeCurrency: isNative,\r\n prevTickPriceQ96: params.prevTickPriceQ96,\r\n hookData: params.hookData,\r\n tickSpacing: params.tickSpacing,\r\n }),\r\n });\r\n\r\n return steps;\r\n}\r\n\r\n/**\r\n * Why a bid cannot be submitted, or `null` when it can.\r\n * Callers use this to disable a bid button with an honest reason.\r\n */\r\nexport function describeBidBlocker(input: {\r\n status: \"pending\" | \"live\" | \"ended\";\r\n amount: bigint;\r\n maxPriceQ96: bigint;\r\n floorPriceQ96: bigint;\r\n tickSpacing?: bigint;\r\n readiness?: BidReadiness;\r\n}): string | null {\r\n if (input.status === \"ended\") return \"This auction has ended - bidding is closed.\";\r\n if (input.status === \"pending\") return \"This auction has not opened yet.\";\r\n if (input.amount <= 0n) return \"Enter a bid amount.\";\r\n if (input.maxPriceQ96 <= 0n) return \"Enter a maximum price.\";\r\n if (input.maxPriceQ96 < input.floorPriceQ96) {\r\n return \"Your maximum price is below the auction's floor price.\";\r\n }\r\n if (input.tickSpacing !== undefined) {\r\n const aligned = alignPriceToTickBoundary(input.maxPriceQ96, input.tickSpacing);\r\n if (aligned < input.floorPriceQ96) {\r\n return \"Your maximum price rounds down below the auction's floor price.\";\r\n }\r\n }\r\n if (input.readiness && !input.readiness.hasBalance) return \"Insufficient balance.\";\r\n return null;\r\n}\r\n"]}
|