@chipi-stack/backend 14.14.0 → 14.15.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/index.js CHANGED
@@ -732,7 +732,8 @@ var ChipiClient = class {
732
732
  throw new shared.ChipiApiError(
733
733
  errorData.message,
734
734
  errorData.code || `HTTP_${response.status}`,
735
- response.status
735
+ response.status,
736
+ data
736
737
  );
737
738
  }
738
739
  return data;
@@ -743,14 +744,15 @@ var ChipiClient = class {
743
744
  async post({
744
745
  endpoint,
745
746
  bearerToken,
746
- body
747
+ body,
748
+ headers
747
749
  }) {
748
750
  try {
749
751
  const url = new URL(`${this.baseUrl}${endpoint}`);
750
752
  this.addVersionParams(url);
751
753
  const response = await fetch(url.toString(), {
752
754
  method: "POST",
753
- headers: this.getHeaders(bearerToken),
755
+ headers: { ...headers, ...this.getHeaders(bearerToken) },
754
756
  body: body ? JSON.stringify(body) : void 0
755
757
  });
756
758
  const data = await response.json();
@@ -759,7 +761,8 @@ var ChipiClient = class {
759
761
  throw new shared.ChipiApiError(
760
762
  errorData.message,
761
763
  errorData.code || `HTTP_${response.status}`,
762
- response.status
764
+ response.status,
765
+ data
763
766
  );
764
767
  }
765
768
  return data;
@@ -786,7 +789,8 @@ var ChipiClient = class {
786
789
  throw new shared.ChipiApiError(
787
790
  errorData.message,
788
791
  errorData.code || `HTTP_${response.status}`,
789
- response.status
792
+ response.status,
793
+ data
790
794
  );
791
795
  }
792
796
  return data;
@@ -813,7 +817,8 @@ var ChipiClient = class {
813
817
  throw new shared.ChipiApiError(
814
818
  errorData.message,
815
819
  errorData.code || `HTTP_${response.status}`,
816
- response.status
820
+ response.status,
821
+ data
817
822
  );
818
823
  }
819
824
  return data;
@@ -838,7 +843,8 @@ var ChipiClient = class {
838
843
  throw new shared.ChipiApiError(
839
844
  errorData.message,
840
845
  errorData.code || `HTTP_${response.status}`,
841
- response.status
846
+ response.status,
847
+ data
842
848
  );
843
849
  }
844
850
  return data;
@@ -3113,8 +3119,149 @@ var ChipiTreasury = class {
3113
3119
  };
3114
3120
  }
3115
3121
  };
3122
+ var DEFAULT_RETRY_INTERVAL_MS = 3e3;
3123
+ var DEFAULT_TIMEOUT_MS = 3e5;
3124
+ function isPending(finalityStatus) {
3125
+ return finalityStatus === void 0 || finalityStatus === "RECEIVED" || finalityStatus === "PENDING" || finalityStatus === "PRE_CONFIRMED";
3126
+ }
3127
+ async function waitForTransaction(transactionHash, options = {}) {
3128
+ const {
3129
+ nodeUrl = shared.WALLET_RPC_ENDPOINTS.CHIPI,
3130
+ retryIntervalMs = DEFAULT_RETRY_INTERVAL_MS,
3131
+ timeoutMs = DEFAULT_TIMEOUT_MS
3132
+ } = options;
3133
+ const deadline = Date.now() + timeoutMs;
3134
+ let lastError;
3135
+ for (; ; ) {
3136
+ try {
3137
+ const receipt = await shared.withRpcFallback(
3138
+ (provider) => provider.getTransactionReceipt(transactionHash),
3139
+ { fallbackUrl: nodeUrl }
3140
+ );
3141
+ if (!isPending(receipt?.finality_status)) {
3142
+ const reverted = receipt.execution_status === "REVERTED";
3143
+ return {
3144
+ transactionHash,
3145
+ success: !reverted,
3146
+ executionStatus: receipt.execution_status,
3147
+ finalityStatus: receipt.finality_status,
3148
+ ...reverted ? { revertReason: receipt.revert_reason ?? "reverted on chain" } : {},
3149
+ events: receipt.events ?? [],
3150
+ ...receipt.block_number !== void 0 ? { blockNumber: receipt.block_number } : {}
3151
+ };
3152
+ }
3153
+ } catch (err) {
3154
+ lastError = err;
3155
+ }
3156
+ if (Date.now() + retryIntervalMs >= deadline) {
3157
+ const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
3158
+ throw new Error(
3159
+ `Timed out after ${timeoutMs}ms waiting for ${transactionHash}${detail}. The transaction may still land; re-check before treating it as failed.`
3160
+ );
3161
+ }
3162
+ await new Promise((resolve) => setTimeout(resolve, retryIntervalMs));
3163
+ }
3164
+ }
3165
+ var InMemoryTxHashStore = class {
3166
+ constructor() {
3167
+ this.used = /* @__PURE__ */ new Set();
3168
+ }
3169
+ consume(transactionHash) {
3170
+ if (this.used.has(transactionHash)) return false;
3171
+ this.used.add(transactionHash);
3172
+ return true;
3173
+ }
3174
+ };
3175
+ var TRANSFER_SELECTOR = BigInt(starknet.hash.getSelectorFromName("Transfer"));
3176
+ function toBigInt2(value) {
3177
+ if (value === void 0) return void 0;
3178
+ try {
3179
+ return BigInt(value);
3180
+ } catch {
3181
+ return void 0;
3182
+ }
3183
+ }
3184
+ function decodeTransfer(event) {
3185
+ if (toBigInt2(event.keys[0]) !== TRANSFER_SELECTOR) return void 0;
3186
+ const [from, to, low, high] = event.keys.length >= 3 ? [event.keys[1], event.keys[2], event.data[0], event.data[1]] : [event.data[0], event.data[1], event.data[2], event.data[3]];
3187
+ const values = [from, to, low, high].map(toBigInt2);
3188
+ if (values.some((v) => v === void 0)) return void 0;
3189
+ const [f, t, l, h] = values;
3190
+ return { from: f, to: t, amount: l + (h << 128n) };
3191
+ }
3192
+ var SessionTxVerifier = class {
3193
+ constructor(config = {}) {
3194
+ const { txHashStore, getReceipt, ...receiptOptions } = config;
3195
+ this.store = txHashStore ?? new InMemoryTxHashStore();
3196
+ this.getReceipt = getReceipt ?? waitForTransaction;
3197
+ this.receiptOptions = receiptOptions;
3198
+ }
3199
+ async verifyTransfer(params) {
3200
+ const { transactionHash, token, from, to, minAmount } = params;
3201
+ const receipt = await this.getReceipt(transactionHash, this.receiptOptions);
3202
+ if (!receipt.success) {
3203
+ return { valid: false, transactionHash, reason: "REVERTED", detail: receipt.revertReason };
3204
+ }
3205
+ const tokenAddress = BigInt(token);
3206
+ const fromAddress = BigInt(from);
3207
+ const toAddress = BigInt(to);
3208
+ let best;
3209
+ for (const event of receipt.events) {
3210
+ if (toBigInt2(event.from_address) !== tokenAddress) continue;
3211
+ const transfer = decodeTransfer(event);
3212
+ if (!transfer || transfer.from !== fromAddress || transfer.to !== toAddress) continue;
3213
+ if (best === void 0 || transfer.amount > best) best = transfer.amount;
3214
+ }
3215
+ if (best === void 0) {
3216
+ return { valid: false, transactionHash, reason: "TRANSFER_NOT_FOUND" };
3217
+ }
3218
+ if (best < minAmount) {
3219
+ return {
3220
+ valid: false,
3221
+ transactionHash,
3222
+ reason: "AMOUNT_TOO_LOW",
3223
+ detail: `paid ${best}, expected at least ${minAmount}`
3224
+ };
3225
+ }
3226
+ const key = "0x" + BigInt(transactionHash).toString(16);
3227
+ if (!await this.store.consume(key)) {
3228
+ return { valid: false, transactionHash, reason: "ALREADY_USED" };
3229
+ }
3230
+ return {
3231
+ valid: true,
3232
+ transactionHash,
3233
+ amount: best,
3234
+ ...receipt.blockNumber !== void 0 ? { blockNumber: receipt.blockNumber } : {}
3235
+ };
3236
+ }
3237
+ };
3238
+
3239
+ // src/ai.ts
3116
3240
  var AI_BASE = "/ai";
3117
3241
  var THINK_ACTIONS = /* @__PURE__ */ new Set(["swap", "supply", "withdraw", "hold"]);
3242
+ var PAID_MAX_ATTEMPTS = 3;
3243
+ var PAID_RETRY_DELAY_MS = 5e3;
3244
+ var padFelt = (value) => "0x" + BigInt(value).toString(16).padStart(64, "0");
3245
+ function sameFelt(a, b) {
3246
+ try {
3247
+ return BigInt(a) === BigInt(b);
3248
+ } catch {
3249
+ return false;
3250
+ }
3251
+ }
3252
+ function sessionTransferOffer(err) {
3253
+ if (!(err instanceof shared.ChipiApiError) || err.status !== 402) return void 0;
3254
+ const details = err.details;
3255
+ return details?.x402?.accepts?.find(
3256
+ (r) => r.scheme === "exact" && r.network === "starknet-mainnet" && r.extra?.paymentMethod === "session-transfer" && sameFelt(r.asset, shared.CONTRACT_ADDRESSES.USDC_MAINNET)
3257
+ );
3258
+ }
3259
+ function thinkInputBytes(params) {
3260
+ const bytes = (text) => new TextEncoder().encode(text).length;
3261
+ const chain = params.chain ?? "starknet";
3262
+ return bytes(JSON.stringify(params.portfolio, null, 2)) + (params.history?.recentDecisions ? bytes(String(params.history.recentDecisions)) : 0) + 2 * bytes(chain);
3263
+ }
3264
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3118
3265
  function isThinkDecision(decision) {
3119
3266
  if (!decision || typeof decision !== "object") return false;
3120
3267
  const d = decision;
@@ -3128,9 +3275,10 @@ function isThinkDecision(decision) {
3128
3275
  return true;
3129
3276
  }
3130
3277
  var ChipiAi = class {
3131
- constructor(client, apiSecretKey) {
3278
+ constructor(client, apiSecretKey, sessions) {
3132
3279
  this.client = client;
3133
3280
  this.apiSecretKey = apiSecretKey;
3281
+ this.sessions = sessions;
3134
3282
  }
3135
3283
  secret() {
3136
3284
  const key = this.apiSecretKey;
@@ -3161,13 +3309,175 @@ var ChipiAi = class {
3161
3309
  *
3162
3310
  * The decision is advice, not an order: check it with `isThinkDecision` and
3163
3311
  * enforce your own limits before executing anything.
3312
+ *
3313
+ * **Paying per call (x402).** With `payWithSession`, a 402 that carries a
3314
+ * session-transfer offer (credits short, x402 on) is paid with the agent's
3315
+ * session key: a USDC `transfer` to the offer's `payTo`, then the same call
3316
+ * again with `X-PAYMENT`. The price must be at most `maxAmount`, `model`
3317
+ * must be payable and the input within `maxPromptBytes`, all checked before
3318
+ * paying. The session must whitelist `transfer` and cap USDC. A payment
3319
+ * that was made but not served is reported in `error.details.xPayment`;
3320
+ * pass it back as `options.xPayment` to retry without paying again.
3321
+ *
3322
+ * @example
3323
+ * ```typescript
3324
+ * const res = await sdk.ai.think(
3325
+ * { portfolio, model: "haiku" },
3326
+ * { payWithSession: { encryptKey, wallet, session, maxAmount: 20_000n } } // $0.02 at most
3327
+ * );
3328
+ * res.x402Payment; // set when the call was paid over x402
3329
+ * ```
3164
3330
  */
3165
- async think(params) {
3331
+ async think(params, options = {}) {
3332
+ try {
3333
+ return await this.postThink(params, options.xPayment);
3334
+ } catch (err) {
3335
+ const offer = options.payWithSession ? sessionTransferOffer(err) : void 0;
3336
+ if (!offer || !options.payWithSession) throw err;
3337
+ return this.payAndThink(params, offer, options.payWithSession);
3338
+ }
3339
+ }
3340
+ postThink(params, xPayment) {
3166
3341
  return this.client.post({
3167
3342
  endpoint: `${AI_BASE}/think`,
3168
3343
  bearerToken: this.secret(),
3169
- body: params
3344
+ body: params,
3345
+ ...xPayment ? { headers: { "X-PAYMENT": xPayment } } : {}
3346
+ });
3347
+ }
3348
+ /**
3349
+ * Pay one think call with a session-key USDC transfer, then send it again
3350
+ * with `X-PAYMENT`. Every check that can refuse the call runs before the
3351
+ * transfer; the transfer counts only once its receipt shows the USDC moved.
3352
+ */
3353
+ async payAndThink(params, offer, pay) {
3354
+ if (!this.sessions) {
3355
+ throw new shared.ChipiApiError(
3356
+ "payWithSession needs the SDK's session client",
3357
+ "X402_NOT_CONFIGURED",
3358
+ 402
3359
+ );
3360
+ }
3361
+ let maxAmount;
3362
+ try {
3363
+ maxAmount = BigInt(pay.maxAmount);
3364
+ } catch {
3365
+ throw new shared.ChipiApiError(
3366
+ `maxAmount must be an integer of USDC base units (6 decimals), got ${String(pay.maxAmount)}; nothing was paid`,
3367
+ "X402_INVALID_MAX_AMOUNT",
3368
+ 400
3369
+ );
3370
+ }
3371
+ const amount = BigInt(offer.maxAmountRequired);
3372
+ if (amount > maxAmount) {
3373
+ throw new shared.ChipiApiError(
3374
+ `The x402 price (${amount} USDC base units) is above maxAmount (${pay.maxAmount}); nothing was paid`,
3375
+ "X402_PRICE_ABOVE_MAX",
3376
+ 402,
3377
+ { x402: { accepts: [offer] } }
3378
+ );
3379
+ }
3380
+ const payable = offer.extra?.payableModels;
3381
+ if (params.model && Array.isArray(payable) && !payable.includes(params.model)) {
3382
+ throw new shared.ChipiApiError(
3383
+ `Model ${params.model} cannot be paid with x402 (payable: ${payable.join(", ")}); nothing was paid`,
3384
+ "X402_MODEL_NOT_PAYABLE",
3385
+ 402,
3386
+ { x402: { accepts: [offer] } }
3387
+ );
3388
+ }
3389
+ const maxBytes = offer.extra?.maxPromptBytes;
3390
+ if (typeof maxBytes === "number" && thinkInputBytes(params) > maxBytes) {
3391
+ throw new shared.ChipiApiError(
3392
+ `Paid calls accept at most ${maxBytes} bytes of portfolio, history and chain; nothing was paid`,
3393
+ "X402_PAYLOAD_TOO_LARGE",
3394
+ 402,
3395
+ { x402: { accepts: [offer] } }
3396
+ );
3397
+ }
3398
+ const payer = pay.wallet.publicKey;
3399
+ const mask = (1n << 128n) - 1n;
3400
+ const transactionHash = await this.sessions.executeTransactionWithSession(
3401
+ {
3402
+ encryptKey: pay.encryptKey,
3403
+ wallet: pay.wallet,
3404
+ session: pay.session,
3405
+ calls: [
3406
+ {
3407
+ contractAddress: offer.asset,
3408
+ entrypoint: "transfer",
3409
+ calldata: [offer.payTo, (amount & mask).toString(), (amount >> 128n).toString()]
3410
+ }
3411
+ ]
3412
+ },
3413
+ this.secret()
3414
+ );
3415
+ const header = {
3416
+ x402Version: 1,
3417
+ scheme: "exact",
3418
+ network: "starknet-mainnet",
3419
+ payload: {
3420
+ signature: { r: padFelt(transactionHash), s: "session" },
3421
+ fromAddress: padFelt(payer),
3422
+ toAddress: padFelt(offer.payTo),
3423
+ amount: amount.toString(),
3424
+ asset: offer.asset,
3425
+ validUntil: Math.floor(Date.now() / 1e3) + offer.maxTimeoutSeconds,
3426
+ nonce: padFelt(transactionHash)
3427
+ }
3428
+ };
3429
+ const xPayment = JSON.stringify(header);
3430
+ const paidError = (message, code, extra = {}) => new shared.ChipiApiError(message, code, 402, {
3431
+ x402: { accepts: [offer] },
3432
+ x402PaymentHash: transactionHash,
3433
+ ...extra
3170
3434
  });
3435
+ let receipt;
3436
+ try {
3437
+ receipt = await waitForTransaction(transactionHash, { timeoutMs: 12e4 });
3438
+ } catch (err) {
3439
+ throw paidError(
3440
+ `No receipt yet for the x402 payment ${transactionHash}: ${err instanceof Error ? err.message : String(err)}`,
3441
+ "X402_PAYMENT_PENDING",
3442
+ { xPayment }
3443
+ );
3444
+ }
3445
+ const paid = await new SessionTxVerifier({
3446
+ txHashStore: { consume: () => true },
3447
+ getReceipt: async () => receipt
3448
+ }).verifyTransfer({
3449
+ transactionHash,
3450
+ token: offer.asset,
3451
+ from: payer,
3452
+ to: offer.payTo,
3453
+ minAmount: amount
3454
+ });
3455
+ if (!paid.valid) {
3456
+ throw paidError(
3457
+ `The x402 payment did not move ${amount} USDC base units to payTo (${paid.reason}). Check the session's whitelist (transfer) and its USDC cap.`,
3458
+ "X402_PAYMENT_FAILED",
3459
+ { reason: paid.reason }
3460
+ );
3461
+ }
3462
+ for (let attempt = 1; ; attempt++) {
3463
+ try {
3464
+ return await this.postThink(params, xPayment);
3465
+ } catch (err) {
3466
+ const retryable = err instanceof shared.ChipiApiError && (err.status === 409 || err.status === 503 && err.details?.retryable === true);
3467
+ if (retryable && attempt < PAID_MAX_ATTEMPTS) {
3468
+ await sleep(PAID_RETRY_DELAY_MS);
3469
+ continue;
3470
+ }
3471
+ if (err instanceof shared.ChipiApiError) {
3472
+ throw new shared.ChipiApiError(err.message, err.code, err.status ?? 402, {
3473
+ ...typeof err.details === "object" && err.details !== null ? err.details : {},
3474
+ x402PaymentHash: transactionHash,
3475
+ xPayment
3476
+ });
3477
+ }
3478
+ throw err;
3479
+ }
3480
+ }
3171
3481
  }
3172
3482
  /**
3173
3483
  * `POST /v1/ai/execute`: unsigned AVNU swap calls (approve + swap) for
@@ -3529,7 +3839,7 @@ var ChipiSDK = class {
3529
3839
  this.sessions = new ChipiSessions(this.client);
3530
3840
  this.exchanges = new ChipiExchanges(this.client);
3531
3841
  this.treasury = new ChipiTreasury(this.client, config.apiSecretKey);
3532
- this.ai = new ChipiAi(this.client, config.apiSecretKey);
3842
+ this.ai = new ChipiAi(this.client, config.apiSecretKey, this.sessions);
3533
3843
  this.executeTransaction = this.executeTransaction.bind(this);
3534
3844
  this.executeTransactionWithSession = this.executeTransactionWithSession.bind(this);
3535
3845
  this.transfer = this.transfer.bind(this);
@@ -3842,49 +4152,6 @@ var ChipiBrowserSDK = class extends ChipiSDK {
3842
4152
  // All methods are inherited from ChipiSDK
3843
4153
  // bearerToken parameter is required for each authenticated request
3844
4154
  };
3845
- var DEFAULT_RETRY_INTERVAL_MS = 3e3;
3846
- var DEFAULT_TIMEOUT_MS = 3e5;
3847
- function isPending(finalityStatus) {
3848
- return finalityStatus === void 0 || finalityStatus === "RECEIVED" || finalityStatus === "PENDING" || finalityStatus === "PRE_CONFIRMED";
3849
- }
3850
- async function waitForTransaction(transactionHash, options = {}) {
3851
- const {
3852
- nodeUrl = shared.WALLET_RPC_ENDPOINTS.CHIPI,
3853
- retryIntervalMs = DEFAULT_RETRY_INTERVAL_MS,
3854
- timeoutMs = DEFAULT_TIMEOUT_MS
3855
- } = options;
3856
- const deadline = Date.now() + timeoutMs;
3857
- let lastError;
3858
- for (; ; ) {
3859
- try {
3860
- const receipt = await shared.withRpcFallback(
3861
- (provider) => provider.getTransactionReceipt(transactionHash),
3862
- { fallbackUrl: nodeUrl }
3863
- );
3864
- if (!isPending(receipt?.finality_status)) {
3865
- const reverted = receipt.execution_status === "REVERTED";
3866
- return {
3867
- transactionHash,
3868
- success: !reverted,
3869
- executionStatus: receipt.execution_status,
3870
- finalityStatus: receipt.finality_status,
3871
- ...reverted ? { revertReason: receipt.revert_reason ?? "reverted on chain" } : {},
3872
- events: receipt.events ?? [],
3873
- ...receipt.block_number !== void 0 ? { blockNumber: receipt.block_number } : {}
3874
- };
3875
- }
3876
- } catch (err) {
3877
- lastError = err;
3878
- }
3879
- if (Date.now() + retryIntervalMs >= deadline) {
3880
- const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
3881
- throw new Error(
3882
- `Timed out after ${timeoutMs}ms waiting for ${transactionHash}${detail}. The transaction may still land; re-check before treating it as failed.`
3883
- );
3884
- }
3885
- await new Promise((resolve) => setTimeout(resolve, retryIntervalMs));
3886
- }
3887
- }
3888
4155
 
3889
4156
  // src/index.ts
3890
4157
  init_encryption2();
@@ -4209,79 +4476,6 @@ function x402Middleware(config) {
4209
4476
  next();
4210
4477
  };
4211
4478
  }
4212
- var InMemoryTxHashStore = class {
4213
- constructor() {
4214
- this.used = /* @__PURE__ */ new Set();
4215
- }
4216
- consume(transactionHash) {
4217
- if (this.used.has(transactionHash)) return false;
4218
- this.used.add(transactionHash);
4219
- return true;
4220
- }
4221
- };
4222
- var TRANSFER_SELECTOR = BigInt(starknet.hash.getSelectorFromName("Transfer"));
4223
- function toBigInt2(value) {
4224
- if (value === void 0) return void 0;
4225
- try {
4226
- return BigInt(value);
4227
- } catch {
4228
- return void 0;
4229
- }
4230
- }
4231
- function decodeTransfer(event) {
4232
- if (toBigInt2(event.keys[0]) !== TRANSFER_SELECTOR) return void 0;
4233
- const [from, to, low, high] = event.keys.length >= 3 ? [event.keys[1], event.keys[2], event.data[0], event.data[1]] : [event.data[0], event.data[1], event.data[2], event.data[3]];
4234
- const values = [from, to, low, high].map(toBigInt2);
4235
- if (values.some((v) => v === void 0)) return void 0;
4236
- const [f, t, l, h] = values;
4237
- return { from: f, to: t, amount: l + (h << 128n) };
4238
- }
4239
- var SessionTxVerifier = class {
4240
- constructor(config = {}) {
4241
- const { txHashStore, getReceipt, ...receiptOptions } = config;
4242
- this.store = txHashStore ?? new InMemoryTxHashStore();
4243
- this.getReceipt = getReceipt ?? waitForTransaction;
4244
- this.receiptOptions = receiptOptions;
4245
- }
4246
- async verifyTransfer(params) {
4247
- const { transactionHash, token, from, to, minAmount } = params;
4248
- const receipt = await this.getReceipt(transactionHash, this.receiptOptions);
4249
- if (!receipt.success) {
4250
- return { valid: false, transactionHash, reason: "REVERTED", detail: receipt.revertReason };
4251
- }
4252
- const tokenAddress = BigInt(token);
4253
- const fromAddress = BigInt(from);
4254
- const toAddress = BigInt(to);
4255
- let best;
4256
- for (const event of receipt.events) {
4257
- if (toBigInt2(event.from_address) !== tokenAddress) continue;
4258
- const transfer = decodeTransfer(event);
4259
- if (!transfer || transfer.from !== fromAddress || transfer.to !== toAddress) continue;
4260
- if (best === void 0 || transfer.amount > best) best = transfer.amount;
4261
- }
4262
- if (best === void 0) {
4263
- return { valid: false, transactionHash, reason: "TRANSFER_NOT_FOUND" };
4264
- }
4265
- if (best < minAmount) {
4266
- return {
4267
- valid: false,
4268
- transactionHash,
4269
- reason: "AMOUNT_TOO_LOW",
4270
- detail: `paid ${best}, expected at least ${minAmount}`
4271
- };
4272
- }
4273
- const key = "0x" + BigInt(transactionHash).toString(16);
4274
- if (!await this.store.consume(key)) {
4275
- return { valid: false, transactionHash, reason: "ALREADY_USED" };
4276
- }
4277
- return {
4278
- valid: true,
4279
- transactionHash,
4280
- amount: best,
4281
- ...receipt.blockNumber !== void 0 ? { blockNumber: receipt.blockNumber } : {}
4282
- };
4283
- }
4284
- };
4285
4479
 
4286
4480
  // src/shhh/index.ts
4287
4481
  init_snip12_hash();