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