@yodlpay/payment-decoder 1.3.6 → 1.3.8

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.
Files changed (2) hide show
  1. package/dist/index.js +142 -70
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -30,8 +30,9 @@ import { tokenlist } from "@yodlpay/tokenlists";
30
30
  import {
31
31
  erc20Abi as erc20Abi2,
32
32
  getAddress,
33
- isAddressEqual,
34
- parseEventLogs
33
+ isAddressEqual as isAddressEqual2,
34
+ parseEventLogs,
35
+ zeroAddress as zeroAddress2
35
36
  } from "viem";
36
37
 
37
38
  // src/abi.ts
@@ -41,6 +42,9 @@ var yodlAbi = getRouterAbi("0.8");
41
42
  var relaySwapAbi = parseAbi([
42
43
  "event Swap(address indexed sender, address indexed recipient, address inputToken, address outputToken, uint256 inputAmount, uint256 outputAmount)"
43
44
  ]);
45
+ var fundsMovementAbi = parseAbi([
46
+ "event FundsMovement(address from, address to, address currency, uint256 amount, bytes metadata)"
47
+ ]);
44
48
 
45
49
  // src/utils.ts
46
50
  import { getTokenByAddress } from "@yodlpay/tokenlists";
@@ -48,9 +52,17 @@ import {
48
52
  erc20Abi,
49
53
  getContract,
50
54
  hexToString,
51
- stringify
55
+ isAddressEqual,
56
+ stringify,
57
+ zeroAddress
52
58
  } from "viem";
53
59
  import { z } from "zod";
60
+
61
+ // src/chains.ts
62
+ import { chains as allChains } from "@yodlpay/tokenlists";
63
+ var chains = allChains.filter((c) => !c.testnet);
64
+
65
+ // src/utils.ts
54
66
  function decodeMemo(memo) {
55
67
  if (!memo || memo === "0x") return "";
56
68
  try {
@@ -62,6 +74,19 @@ function decodeMemo(memo) {
62
74
  var erc20String = z.string().max(64).transform((s) => s.replace(/\p{Cc}/gu, ""));
63
75
  var erc20Decimals = z.number().int().nonnegative().max(36);
64
76
  async function resolveToken(address, chainId2, client) {
77
+ if (isAddressEqual(address, zeroAddress)) {
78
+ const chain = chains.find((c) => c.id === chainId2);
79
+ if (!chain) {
80
+ throw new Error(`Native token requested for unknown chainId ${chainId2}`);
81
+ }
82
+ return {
83
+ chainId: chainId2,
84
+ address,
85
+ name: chain.nativeCurrency.name,
86
+ symbol: chain.nativeCurrency.symbol,
87
+ decimals: chain.nativeCurrency.decimals
88
+ };
89
+ }
65
90
  try {
66
91
  return getTokenByAddress(address, chainId2);
67
92
  } catch {
@@ -85,7 +110,7 @@ async function resolveToken(address, chainId2, client) {
85
110
  function decodeYodlFromLogs(logs, routerAddress) {
86
111
  const parsed = parseEventLogs({
87
112
  abi: yodlAbi,
88
- logs: logs.filter((l) => isAddressEqual(l.address, routerAddress)),
113
+ logs: logs.filter((l) => isAddressEqual2(l.address, routerAddress)),
89
114
  eventName: "Yodl",
90
115
  strict: true
91
116
  });
@@ -132,8 +157,22 @@ function extractTokenTransfers(logs) {
132
157
  return [{ token: log.address, from, to, amount: value }];
133
158
  });
134
159
  }
160
+ function extractFundsMovements(logs) {
161
+ const parsed = parseEventLogs({
162
+ abi: fundsMovementAbi,
163
+ logs: [...logs],
164
+ eventName: "FundsMovement",
165
+ strict: false
166
+ });
167
+ return parsed.flatMap((log) => {
168
+ if (!log.args) return [];
169
+ const { from, to, currency, amount } = log.args;
170
+ if (!from || !to || !currency || amount === void 0) return [];
171
+ return [{ emitter: log.address, from, to, currency, amount }];
172
+ });
173
+ }
135
174
  function isKnownToken(address) {
136
- return tokenlist.some((t) => isAddressEqual(t.address, address));
175
+ return tokenlist.some((t) => isAddressEqual2(t.address, address));
137
176
  }
138
177
  function transferSignature(t) {
139
178
  return `${getAddress(t.from)}:${getAddress(t.to)}:${t.amount}`;
@@ -180,7 +219,7 @@ function detectMirrorTokens(allTransfers) {
180
219
  return mirrors;
181
220
  }
182
221
  function findInputTransfer(transfers, sender) {
183
- const fromSender = transfers.filter((t) => isAddressEqual(t.from, sender));
222
+ const fromSender = transfers.filter((t) => isAddressEqual2(t.from, sender));
184
223
  if (fromSender.length === 0) return void 0;
185
224
  const maxAmount = fromSender.reduce(
186
225
  (max, t) => t.amount > max ? t.amount : max,
@@ -200,52 +239,68 @@ function findInputTransfer(transfers, sender) {
200
239
  }
201
240
  function inferSwapFromTransfers(logs, sender, yodlToken) {
202
241
  const transfers = extractTokenTransfers(logs);
242
+ const tokenOutAmount = transfers.filter(
243
+ (t) => isAddressEqual2(t.to, sender) && isAddressEqual2(t.token, yodlToken)
244
+ ).reduce((sum, t) => sum + t.amount, 0n);
245
+ if (tokenOutAmount === 0n) return void 0;
203
246
  const outgoingDifferentToken = transfers.filter(
204
- (t) => isAddressEqual(t.from, sender) && !isAddressEqual(t.token, yodlToken)
247
+ (t) => isAddressEqual2(t.from, sender) && !isAddressEqual2(t.token, yodlToken)
205
248
  );
206
- if (outgoingDifferentToken.length === 0) return void 0;
207
- const byToken = /* @__PURE__ */ new Map();
208
- for (const t of outgoingDifferentToken) {
209
- const key = getAddress(t.token);
210
- const existing = byToken.get(key);
211
- if (existing) {
212
- existing.gross += t.amount;
213
- } else {
214
- byToken.set(key, { token: t.token, gross: t.amount, refund: 0n });
249
+ if (outgoingDifferentToken.length > 0) {
250
+ const byToken = /* @__PURE__ */ new Map();
251
+ for (const t of outgoingDifferentToken) {
252
+ const key = getAddress(t.token);
253
+ const existing = byToken.get(key);
254
+ if (existing) {
255
+ existing.gross += t.amount;
256
+ } else {
257
+ byToken.set(key, { token: t.token, gross: t.amount, refund: 0n });
258
+ }
215
259
  }
216
- }
217
- const incomingNonYodl = transfers.filter(
218
- (t) => isAddressEqual(t.to, sender) && !isAddressEqual(t.token, yodlToken)
219
- );
220
- for (const t of incomingNonYodl) {
221
- const key = getAddress(t.token);
222
- const existing = byToken.get(key);
223
- if (existing) {
224
- existing.refund += t.amount;
260
+ const incomingNonYodl = transfers.filter(
261
+ (t) => isAddressEqual2(t.to, sender) && !isAddressEqual2(t.token, yodlToken)
262
+ );
263
+ for (const t of incomingNonYodl) {
264
+ const key = getAddress(t.token);
265
+ const existing = byToken.get(key);
266
+ if (existing) {
267
+ existing.refund += t.amount;
268
+ }
269
+ }
270
+ let tokenIn;
271
+ let tokenInAmount = 0n;
272
+ for (const { token, gross, refund } of byToken.values()) {
273
+ const net = gross - refund;
274
+ if (net > tokenInAmount) {
275
+ tokenIn = token;
276
+ tokenInAmount = net;
277
+ }
225
278
  }
279
+ if (tokenIn && tokenInAmount > 0n) {
280
+ return {
281
+ tokenIn,
282
+ tokenOut: yodlToken,
283
+ tokenInAmount,
284
+ tokenOutAmount
285
+ };
286
+ }
287
+ return void 0;
226
288
  }
227
- let tokenIn;
228
- let tokenInAmount = 0n;
229
- for (const { token, gross, refund } of byToken.values()) {
230
- const net = gross - refund;
231
- if (net > tokenInAmount) {
232
- tokenIn = token;
233
- tokenInAmount = net;
289
+ let nativeInAmount = 0n;
290
+ for (const movement of extractFundsMovements(logs)) {
291
+ if (!isAddressEqual2(movement.currency, zeroAddress2)) continue;
292
+ if (isAddressEqual2(movement.from, sender)) {
293
+ nativeInAmount += movement.amount;
294
+ }
295
+ if (isAddressEqual2(movement.to, sender)) {
296
+ nativeInAmount -= movement.amount;
234
297
  }
235
298
  }
236
- if (!tokenIn || tokenInAmount <= 0n) return void 0;
237
- const incomingYodlToken = transfers.filter(
238
- (t) => isAddressEqual(t.to, sender) && isAddressEqual(t.token, yodlToken)
239
- );
240
- const tokenOutAmount = incomingYodlToken.reduce(
241
- (sum, t) => sum + t.amount,
242
- 0n
243
- );
244
- if (tokenOutAmount === 0n) return void 0;
299
+ if (nativeInAmount <= 0n) return void 0;
245
300
  return {
246
- tokenIn,
301
+ tokenIn: zeroAddress2,
247
302
  tokenOut: yodlToken,
248
- tokenInAmount,
303
+ tokenInAmount: nativeInAmount,
249
304
  tokenOutAmount
250
305
  };
251
306
  }
@@ -267,12 +322,6 @@ import { decodeFunctionData, toFunctionSelector } from "viem";
267
322
  // src/validation.ts
268
323
  import { isAddress, isHash, isHex } from "viem";
269
324
  import { z as z2 } from "zod";
270
-
271
- // src/chains.ts
272
- import { chains as allChains } from "@yodlpay/tokenlists";
273
- var chains = allChains.filter((c) => !c.testnet);
274
-
275
- // src/validation.ts
276
325
  var validChainIds = chains.map((c) => c.id);
277
326
  var txHashSchema = z2.string().refine((val) => isHash(val), "Invalid transaction hash");
278
327
  var chainIdSchema = z2.coerce.number().int().refine((id) => validChainIds.includes(id), {
@@ -408,8 +457,10 @@ async function fetchAcrossDepositByTx(hash) {
408
457
  // src/bridges/relay-bridge.ts
409
458
  import { getRouter } from "@yodlpay/tokenlists";
410
459
  import {
411
- isAddressEqual as isAddressEqual2,
412
- parseEventLogs as parseEventLogs2
460
+ isAddress as isAddress2,
461
+ isAddressEqual as isAddressEqual3,
462
+ parseEventLogs as parseEventLogs2,
463
+ zeroAddress as zeroAddress3
413
464
  } from "viem";
414
465
  import { entryPoint08Abi, entryPoint08Address } from "viem/account-abstraction";
415
466
 
@@ -502,7 +553,7 @@ function extractSenderFromSource(sourceReceipt) {
502
553
  const userOps = parseEventLogs2({
503
554
  abi: entryPoint08Abi,
504
555
  logs: sourceReceipt.logs.filter(
505
- (l) => isAddressEqual2(l.address, entryPoint08Address)
556
+ (l) => isAddressEqual3(l.address, entryPoint08Address)
506
557
  ),
507
558
  eventName: "UserOperationEvent"
508
559
  });
@@ -552,12 +603,15 @@ function parseRelayResponse(request) {
552
603
  if (!inTx?.hash || !inTx?.chainId || !outTx?.hash || !outTx?.chainId) {
553
604
  throw new NoBridgeFoundError();
554
605
  }
606
+ const rawInputToken = data?.metadata?.currencyIn?.currency?.address;
607
+ const inputToken = rawInputToken && isAddress2(rawInputToken) ? rawInputToken : void 0;
555
608
  return {
556
609
  sourceChainId: inTx.chainId,
557
610
  sourceTxHash: inTx.hash,
558
611
  destinationChainId: outTx.chainId,
559
612
  destinationTxHash: outTx.hash,
560
- inputAmountGross: BigInt(data?.metadata?.currencyIn?.amount ?? 0)
613
+ inputAmountGross: BigInt(data?.metadata?.currencyIn?.amount ?? 0),
614
+ inputToken
561
615
  };
562
616
  }
563
617
  async function decodeRelayBridgePayment(hash, clients, options = {}) {
@@ -567,7 +621,8 @@ async function decodeRelayBridgePayment(hash, clients, options = {}) {
567
621
  sourceTxHash,
568
622
  destinationChainId,
569
623
  destinationTxHash,
570
- inputAmountGross
624
+ inputAmountGross,
625
+ inputToken
571
626
  } = parseRelayResponse(request);
572
627
  const destProvider = clients[destinationChainId];
573
628
  const sourceProvider = clients[sourceChainId];
@@ -593,16 +648,33 @@ async function decodeRelayBridgePayment(hash, clients, options = {}) {
593
648
  destProvider.getBlock({ blockNumber: destReceipt.blockNumber }),
594
649
  destProvider.getTransaction({ hash: destinationTxHash })
595
650
  ]);
596
- const tokens = await resolveBridgeTokens(
597
- sourceReceipt,
598
- destReceipt,
599
- yodlEvent,
600
- sender,
601
- inputAmountGross,
602
- sourceChainId,
603
- destinationChainId,
604
- clients
605
- );
651
+ let tokens;
652
+ try {
653
+ tokens = await resolveBridgeTokens(
654
+ sourceReceipt,
655
+ destReceipt,
656
+ yodlEvent,
657
+ sender,
658
+ inputAmountGross,
659
+ sourceChainId,
660
+ destinationChainId,
661
+ clients
662
+ );
663
+ } catch (error) {
664
+ if (error instanceof ExpectedDecodeError && inputToken && isAddressEqual3(inputToken, zeroAddress3) && inputAmountGross > 0n) {
665
+ const swapEvent = decodeSwapFromLogs(destReceipt.logs);
666
+ const tokenOutAmountGross = swapEvent ? swapEvent.tokenOutAmount : yodlEvent.amount;
667
+ tokens = {
668
+ tokenIn: inputToken,
669
+ tokenInAmount: inputAmountGross,
670
+ tokenOut: yodlEvent.token,
671
+ tokenOutAmount: yodlEvent.amount,
672
+ tokenOutAmountGross
673
+ };
674
+ } else {
675
+ throw error;
676
+ }
677
+ }
606
678
  const bridge = {
607
679
  sourceChainId,
608
680
  sourceTxHash,
@@ -777,7 +849,7 @@ async function decodeBridgePayment(hash, clients, options = {}) {
777
849
 
778
850
  // src/core/payment-decoder.ts
779
851
  import { getRouter as getRouter3 } from "@yodlpay/tokenlists";
780
- import { isAddressEqual as isAddressEqual3 } from "viem";
852
+ import { isAddressEqual as isAddressEqual4 } from "viem";
781
853
  async function tryDecodeBridge(hash, clients, options = {}) {
782
854
  try {
783
855
  return await decodeBridgePayment(hash, clients, options);
@@ -809,7 +881,7 @@ async function decodePayment(hash, chainId2, clients, cachedReceipt) {
809
881
  return { type: "swap", ...paymentEvent2, ...swapEvent };
810
882
  }
811
883
  const effectiveSender = extractSenderFromSource(receipt);
812
- const isSameChainPayment = isAddressEqual3(
884
+ const isSameChainPayment = isAddressEqual4(
813
885
  effectiveSender,
814
886
  yodlEvent.sender
815
887
  );
@@ -848,11 +920,11 @@ async function decodePayment(hash, chainId2, clients, cachedReceipt) {
848
920
  // src/core/yodl-payment.ts
849
921
  import {
850
922
  formatUnits,
851
- isAddressEqual as isAddressEqual4,
852
- zeroAddress
923
+ isAddressEqual as isAddressEqual5,
924
+ zeroAddress as zeroAddress4
853
925
  } from "viem";
854
926
  async function buildTokenInfo(params, clients) {
855
- const sameToken = isAddressEqual4(params.tokenIn, params.tokenOut) && params.inChainId === params.outChainId;
927
+ const sameToken = isAddressEqual5(params.tokenIn, params.tokenOut) && params.inChainId === params.outChainId;
856
928
  const [inToken, outToken] = sameToken ? await resolveToken(
857
929
  params.tokenIn,
858
930
  params.inChainId,
@@ -965,7 +1037,7 @@ async function decodeYodlPayment(txHash2, chainId2, clients, cachedReceipt) {
965
1037
  cachedReceipt
966
1038
  );
967
1039
  const firstWebhook = paymentInfo.webhooks[0];
968
- const processorAddress = firstWebhook?.webhookAddress ?? zeroAddress;
1040
+ const processorAddress = firstWebhook?.webhookAddress ?? zeroAddress4;
969
1041
  const processorMemo = firstWebhook?.memo ?? "";
970
1042
  const tokenInfo = await extractTokenInfo(
971
1043
  paymentInfo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yodlpay/payment-decoder",
3
- "version": "1.3.6",
3
+ "version": "1.3.8",
4
4
  "description": "Decode Yodl payment hashes into structured payment data",
5
5
  "keywords": [
6
6
  "yodl",