@yodlpay/payment-decoder 1.2.1 → 1.3.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/README.md CHANGED
@@ -38,6 +38,23 @@ Decode a Yodl payment transaction into a structured `YodlPayment` object.
38
38
  - `chainId` - Chain ID where the transaction was executed
39
39
  - `clients` - Map of chainId → PublicClient (must include all chains needed for bridges)
40
40
 
41
+ ### `detectChain(hash, clients)`
42
+
43
+ Auto-detect which chain a transaction belongs to by trying all configured chains in parallel. Useful when the chain ID is unknown.
44
+
45
+ - `hash` - Transaction hash to look up
46
+ - `clients` - Map of chainId → PublicClient
47
+
48
+ Returns `{ chainId, receipt }` — the receipt is included so it can be passed to `decodePayment` / `decodeYodlPayment` to avoid a duplicate RPC call.
49
+
50
+ ```ts
51
+ import { detectChain, decodeYodlPayment, createClients } from "@yodlpay/payment-decoder";
52
+
53
+ const clients = createClients();
54
+ const { chainId, receipt } = await detectChain("0x1234...abcd", clients);
55
+ const payment = await decodeYodlPayment("0x1234...abcd", chainId, clients, receipt);
56
+ ```
57
+
41
58
  ### `ChainClients`
42
59
 
43
60
  Type alias for the clients map: `Record<number, PublicClient>`
@@ -47,18 +64,22 @@ Type alias for the clients map: `Record<number, PublicClient>`
47
64
  Decode a transaction directly from the command line. Outputs JSON to stdout.
48
65
 
49
66
  ```bash
50
- bun run decode <txHash> <chainId>
67
+ bun run decode <txHash> [chainId]
51
68
  ```
52
69
 
53
70
  **Arguments:**
54
71
 
55
72
  - `txHash` - The transaction hash (64 hex characters with `0x` prefix)
56
- - `chainId` - The chain ID where the transaction occurred (e.g., `8453` for Base, `42161` for Arbitrum)
73
+ - `chainId` *(optional)* - The chain ID where the transaction occurred (e.g., `8453` for Base, `42161` for Arbitrum). If omitted, the chain is auto-detected.
57
74
 
58
- **Example:**
75
+ **Examples:**
59
76
 
60
77
  ```bash
78
+ # With explicit chain ID
61
79
  bun run decode 0xe7ecad85dcfb6b4e25c833fa3617a45cf34df505cb698e04ad7d75a39032158f 42161
80
+
81
+ # Auto-detect chain
82
+ bun run decode 0xe7ecad85dcfb6b4e25c833fa3617a45cf34df505cb698e04ad7d75a39032158f
62
83
  ```
63
84
 
64
85
  The CLI uses public RPC endpoints. For production use, configure your own RPC URLs via the library API.
package/dist/index.d.ts CHANGED
@@ -1,8 +1,16 @@
1
- import { PublicClient, Address, Hex, Log } from 'viem';
1
+ import { PublicClient, Hex, TransactionReceipt, Address, Log } from 'viem';
2
2
  import { TokenInfo } from '@yodlpay/tokenlists';
3
3
 
4
4
  type ChainClients = Record<number, PublicClient>;
5
5
  declare function createClients(): ChainClients;
6
+ /**
7
+ * Auto-detect which chain a transaction belongs to by trying all configured
8
+ * chains in parallel. Returns both the chain ID and the receipt for reuse.
9
+ */
10
+ declare function detectChain(hash: Hex, clients: ChainClients): Promise<{
11
+ chainId: number;
12
+ receipt: TransactionReceipt;
13
+ }>;
6
14
 
7
15
  type ServiceProvider = "relay";
8
16
  interface Webhook {
@@ -77,6 +85,7 @@ interface DecodedYodlEvent {
77
85
  token: Address;
78
86
  amount: bigint;
79
87
  memo: string;
88
+ logIndex: number;
80
89
  }
81
90
  /**
82
91
  * Decode Yodl payment event from transaction logs.
@@ -91,7 +100,7 @@ declare class NoYodlEventError extends Error {
91
100
  constructor(message?: string);
92
101
  }
93
102
 
94
- declare function decodePayment(hash: Hex, chainId: number, clients: ChainClients): Promise<PaymentInfo>;
103
+ declare function decodePayment(hash: Hex, chainId: number, clients: ChainClients, cachedReceipt?: TransactionReceipt): Promise<PaymentInfo>;
95
104
 
96
105
  /**
97
106
  * Decode a bridge transaction given any hash (source or destination).
@@ -105,6 +114,6 @@ declare function decodeBridgePayment(hash: Hex, clients: ChainClients): Promise<
105
114
  type: "bridge";
106
115
  }>>;
107
116
 
108
- declare function decodeYodlPayment(txHash: Hex, chainId: number, clients: ChainClients): Promise<YodlPayment>;
117
+ declare function decodeYodlPayment(txHash: Hex, chainId: number, clients: ChainClients, cachedReceipt?: TransactionReceipt): Promise<YodlPayment>;
109
118
 
110
- export { type BridgeInfo, type ChainClients, type DecodedYodlEvent, NoBridgeFoundError, NoYodlEventError, type PaymentEvent, type PaymentInfo, type ServiceProvider, type SwapInfo, type TokenInInfo, type TokenOutInfo, type Webhook, type YodlPayment, createClients, decodeBridgePayment, decodePayment, decodeYodlFromLogs, decodeYodlPayment };
119
+ export { type BridgeInfo, type ChainClients, type DecodedYodlEvent, NoBridgeFoundError, NoYodlEventError, type PaymentEvent, type PaymentInfo, type ServiceProvider, type SwapInfo, type TokenInInfo, type TokenOutInfo, type Webhook, type YodlPayment, createClients, decodeBridgePayment, decodePayment, decodeYodlFromLogs, decodeYodlPayment, detectChain };
package/dist/index.js CHANGED
@@ -1,12 +1,19 @@
1
1
  // src/clients.ts
2
2
  import { chains } from "@yodlpay/tokenlists";
3
- import { createPublicClient, http } from "viem";
3
+ import {
4
+ createPublicClient,
5
+ http
6
+ } from "viem";
7
+ var rpcOverrides = {
8
+ 137: "https://polygon-bor-rpc.publicnode.com"
9
+ // polygon-rpc.com is down
10
+ };
4
11
  function createClients() {
5
12
  const clients = {};
6
13
  for (const chain of chains) {
7
14
  clients[chain.id] = createPublicClient({
8
15
  chain,
9
- transport: http()
16
+ transport: http(rpcOverrides[chain.id])
10
17
  });
11
18
  }
12
19
  return clients;
@@ -16,11 +23,32 @@ function getClient(clients, chainId) {
16
23
  if (!client) throw new Error(`No client configured for chain ${chainId}`);
17
24
  return client;
18
25
  }
26
+ async function detectChain(hash, clients) {
27
+ const entries = Object.entries(clients).map(([id, client]) => ({
28
+ chainId: Number(id),
29
+ client
30
+ }));
31
+ const results = await Promise.allSettled(
32
+ entries.map(async ({ chainId, client }) => {
33
+ const receipt = await client.getTransactionReceipt({ hash });
34
+ return { chainId, receipt };
35
+ })
36
+ );
37
+ const found = results.find(
38
+ (r) => r.status === "fulfilled"
39
+ );
40
+ if (!found) {
41
+ throw new Error(`Transaction ${hash} not found on any configured chain`);
42
+ }
43
+ return found.value;
44
+ }
19
45
 
20
46
  // src/decode-utils.ts
47
+ import { tokenlist } from "@yodlpay/tokenlists";
21
48
  import {
22
49
  decodeEventLog,
23
- erc20Abi,
50
+ erc20Abi as erc20Abi2,
51
+ getAddress,
24
52
  isAddressEqual
25
53
  } from "viem";
26
54
 
@@ -87,7 +115,13 @@ function isExpectedDecodeError(error) {
87
115
  }
88
116
 
89
117
  // src/utils.ts
90
- import { hexToString } from "viem";
118
+ import { getTokenByAddress } from "@yodlpay/tokenlists";
119
+ import {
120
+ erc20Abi,
121
+ getContract,
122
+ hexToString
123
+ } from "viem";
124
+ import { z } from "zod";
91
125
  function decodeMemo(memo) {
92
126
  if (!memo || memo === "0x") return "";
93
127
  try {
@@ -96,6 +130,27 @@ function decodeMemo(memo) {
96
130
  return "";
97
131
  }
98
132
  }
133
+ var erc20String = z.string().max(64).transform((s) => s.replace(/\p{Cc}/gu, ""));
134
+ var erc20Decimals = z.number().int().nonnegative().max(36);
135
+ async function resolveToken(address, chainId, client) {
136
+ try {
137
+ return getTokenByAddress(address, chainId);
138
+ } catch {
139
+ const token = getContract({ address, abi: erc20Abi, client });
140
+ const [name, symbol, decimals] = await Promise.all([
141
+ token.read.name(),
142
+ token.read.symbol(),
143
+ token.read.decimals()
144
+ ]);
145
+ return {
146
+ chainId,
147
+ address,
148
+ name: erc20String.parse(name),
149
+ symbol: erc20String.parse(symbol),
150
+ decimals: erc20Decimals.parse(decimals)
151
+ };
152
+ }
153
+ }
99
154
 
100
155
  // src/decode-utils.ts
101
156
  function* matchingEvents(logs, options) {
@@ -146,14 +201,15 @@ function decodeYodlFromLogs(logs, routerAddress) {
146
201
  address: routerAddress,
147
202
  context: "Yodl event"
148
203
  },
149
- (decoded) => {
204
+ (decoded, log) => {
150
205
  const args = decoded.args;
151
206
  return {
152
207
  sender: args.sender,
153
208
  receiver: args.receiver,
154
209
  token: args.token,
155
210
  amount: args.amount,
156
- memo: decodeMemo(args.memo)
211
+ memo: decodeMemo(args.memo),
212
+ logIndex: log.logIndex ?? 0
157
213
  };
158
214
  }
159
215
  );
@@ -177,7 +233,7 @@ function decodeSwapFromLogs(logs) {
177
233
  function extractTokenTransfers(logs) {
178
234
  return collectEventsFromLogs(
179
235
  logs,
180
- { abi: erc20Abi, eventName: "Transfer", context: "ERC20 Transfer" },
236
+ { abi: erc20Abi2, eventName: "Transfer", context: "ERC20 Transfer" },
181
237
  (decoded, log) => {
182
238
  const args = decoded.args;
183
239
  return {
@@ -189,13 +245,66 @@ function extractTokenTransfers(logs) {
189
245
  }
190
246
  );
191
247
  }
248
+ function isKnownToken(address) {
249
+ return tokenlist.some((t) => isAddressEqual(t.address, address));
250
+ }
251
+ function transferSignature(t) {
252
+ return `${getAddress(t.from)}:${getAddress(t.to)}:${t.amount}`;
253
+ }
254
+ function areMirrorTransfers(a, b) {
255
+ if (a.length !== b.length) return false;
256
+ const counts = /* @__PURE__ */ new Map();
257
+ for (const t of a) {
258
+ const key = transferSignature(t);
259
+ counts.set(key, (counts.get(key) ?? 0) + 1);
260
+ }
261
+ for (const t of b) {
262
+ const key = transferSignature(t);
263
+ const count = counts.get(key);
264
+ if (!count) return false;
265
+ if (count === 1) counts.delete(key);
266
+ else counts.set(key, count - 1);
267
+ }
268
+ return counts.size === 0;
269
+ }
270
+ function detectMirrorTokens(allTransfers) {
271
+ const mirrors = /* @__PURE__ */ new Map();
272
+ const byToken = Map.groupBy(allTransfers, (t) => getAddress(t.token));
273
+ const tokens = [...byToken.keys()];
274
+ for (const [i, tokenA] of tokens.entries()) {
275
+ if (mirrors.has(tokenA)) continue;
276
+ const transfersA = byToken.get(tokenA) ?? [];
277
+ for (const tokenB of tokens.slice(i + 1)) {
278
+ if (mirrors.has(tokenB)) continue;
279
+ const transfersB = byToken.get(tokenB) ?? [];
280
+ if (!areMirrorTransfers(transfersA, transfersB)) continue;
281
+ if (isKnownToken(tokenB) && !isKnownToken(tokenA)) {
282
+ mirrors.set(tokenA, tokenB);
283
+ } else {
284
+ mirrors.set(tokenB, tokenA);
285
+ }
286
+ }
287
+ }
288
+ return mirrors;
289
+ }
192
290
  function findInputTransfer(transfers, sender) {
193
291
  const fromSender = transfers.filter((t) => isAddressEqual(t.from, sender));
194
292
  if (fromSender.length === 0) return void 0;
195
- const largest = fromSender.reduce(
196
- (max, t) => t.amount > max.amount ? t : max
293
+ const maxAmount = fromSender.reduce(
294
+ (max, t) => t.amount > max ? t.amount : max,
295
+ 0n
197
296
  );
198
- return { token: largest.token, amount: largest.amount };
297
+ let candidates = fromSender.filter((t) => t.amount === maxAmount);
298
+ if (candidates.length > 1) {
299
+ const mirrors = detectMirrorTokens(transfers);
300
+ const filtered = candidates.filter(
301
+ (t) => !mirrors.has(getAddress(t.token))
302
+ );
303
+ if (filtered.length > 0) candidates = filtered;
304
+ }
305
+ const result = candidates.find((t) => isKnownToken(t.token)) ?? candidates[0];
306
+ if (!result) return void 0;
307
+ return { token: result.token, amount: result.amount };
199
308
  }
200
309
  function toBlockTimestamp(block) {
201
310
  return new Date(Number(block.timestamp) * 1e3);
@@ -218,18 +327,20 @@ import { decodeFunctionData, toFunctionSelector } from "viem";
218
327
  // src/validation.ts
219
328
  import { chains as chains2 } from "@yodlpay/tokenlists";
220
329
  import { isAddress, isHex } from "viem";
221
- import { z } from "zod";
330
+ import { z as z2 } from "zod";
222
331
  var validChainIds = chains2.map((c) => c.id);
223
- var ArgsSchema = z.tuple([
224
- z.string().regex(/^0x[a-fA-F0-9]{64}$/, "Invalid transaction hash").transform((val) => val),
225
- z.coerce.number().int().refine((id) => validChainIds.includes(id), {
226
- message: `Chain ID must be one of: ${validChainIds.join(", ")}`
227
- })
332
+ var txHashSchema = z2.string().regex(/^0x[a-fA-F0-9]{64}$/, "Invalid transaction hash").transform((val) => val);
333
+ var chainIdSchema = z2.coerce.number().int().refine((id) => validChainIds.includes(id), {
334
+ message: `Chain ID must be one of: ${validChainIds.join(", ")}`
335
+ });
336
+ var ArgsSchema = z2.union([
337
+ z2.tuple([txHashSchema, chainIdSchema]),
338
+ z2.tuple([txHashSchema])
228
339
  ]);
229
- var WebhooksSchema = z.array(
230
- z.object({
231
- webhookAddress: z.string().refine((val) => isAddress(val)),
232
- payload: z.array(z.string().refine((val) => isHex(val)))
340
+ var WebhooksSchema = z2.array(
341
+ z2.object({
342
+ webhookAddress: z2.string().refine((val) => isAddress(val)),
343
+ payload: z2.array(z2.string().refine((val) => isHex(val)))
233
344
  }).transform((webhook) => ({
234
345
  ...webhook,
235
346
  memo: webhook.payload[0] ? decodeMemo(webhook.payload[0]) : ""
@@ -267,7 +378,7 @@ function extractEmbeddedParams(data) {
267
378
  }
268
379
 
269
380
  // src/relay-bridge.ts
270
- import { getRouter, getTokenByAddress } from "@yodlpay/tokenlists";
381
+ import { getRouter } from "@yodlpay/tokenlists";
271
382
  import {
272
383
  decodeEventLog as decodeEventLog2,
273
384
  isAddressEqual as isAddressEqual2
@@ -301,15 +412,15 @@ async function fetchRelayRequest(hash) {
301
412
  }
302
413
 
303
414
  // src/relay-bridge.ts
304
- function calculateOutputAmountGross(inputAmount, inputToken, inputChainId, outputToken, outputChainId) {
305
- const { decimals: inputDecimals } = getTokenByAddress(
306
- inputToken,
307
- inputChainId
308
- );
309
- const { decimals: outputDecimals } = getTokenByAddress(
310
- outputToken,
311
- outputChainId
312
- );
415
+ async function calculateOutputAmountGross(inputAmount, inputToken, inputChainId, outputToken, outputChainId, clients) {
416
+ const [{ decimals: inputDecimals }, { decimals: outputDecimals }] = await Promise.all([
417
+ resolveToken(inputToken, inputChainId, getClient(clients, inputChainId)),
418
+ resolveToken(
419
+ outputToken,
420
+ outputChainId,
421
+ getClient(clients, outputChainId)
422
+ )
423
+ ]);
313
424
  const decimalDiff = inputDecimals - outputDecimals;
314
425
  if (decimalDiff === 0) return inputAmount;
315
426
  if (decimalDiff > 0) return inputAmount / 10n ** BigInt(decimalDiff);
@@ -333,7 +444,7 @@ async function extractSenderFromSource(sourceReceipt, sourceProvider, sourceHash
333
444
  const sourceTx = await sourceProvider.getTransaction({ hash: sourceHash });
334
445
  return sourceTx.from;
335
446
  }
336
- function resolveBridgeTokens(sourceReceipt, destReceipt, yodlEvent, sender, inputAmountGross, inputChainId, outputChainId) {
447
+ async function resolveBridgeTokens(sourceReceipt, destReceipt, yodlEvent, sender, inputAmountGross, inputChainId, outputChainId, clients) {
337
448
  const sourceTransfers = extractTokenTransfers(sourceReceipt.logs);
338
449
  const inputTransfer = findInputTransfer(sourceTransfers, sender);
339
450
  const tokenIn = inputTransfer?.token;
@@ -350,12 +461,13 @@ function resolveBridgeTokens(sourceReceipt, destReceipt, yodlEvent, sender, inpu
350
461
  if (swapEvent !== void 0) {
351
462
  tokenOutAmountGross = swapEvent.tokenOutAmount;
352
463
  } else if (inputAmountGross > 0n && tokenIn && tokenOut) {
353
- tokenOutAmountGross = calculateOutputAmountGross(
464
+ tokenOutAmountGross = await calculateOutputAmountGross(
354
465
  inputAmountGross,
355
466
  tokenIn,
356
467
  inputChainId,
357
468
  tokenOut,
358
- outputChainId
469
+ outputChainId,
470
+ clients
359
471
  );
360
472
  }
361
473
  return {
@@ -408,14 +520,15 @@ async function decodeBridgePayment(hash, clients) {
408
520
  destProvider.getTransaction({ hash: destinationTxHash }),
409
521
  extractSenderFromSource(sourceReceipt, sourceProvider, sourceTxHash)
410
522
  ]);
411
- const tokens = resolveBridgeTokens(
523
+ const tokens = await resolveBridgeTokens(
412
524
  sourceReceipt,
413
525
  destReceipt,
414
526
  yodlEvent,
415
527
  sender,
416
528
  inputAmountGross,
417
529
  sourceChainId,
418
- destinationChainId
530
+ destinationChainId,
531
+ clients
419
532
  );
420
533
  const bridge = {
421
534
  sourceChainId,
@@ -447,16 +560,17 @@ async function tryDecodeBridge(hash, clients) {
447
560
  return void 0;
448
561
  }
449
562
  }
450
- async function decodePayment(hash, chainId, clients) {
563
+ async function decodePayment(hash, chainId, clients, cachedReceipt) {
451
564
  const provider = getClient(clients, chainId);
452
- const receipt = await provider.getTransactionReceipt({ hash });
565
+ const receipt = cachedReceipt ?? await provider.getTransactionReceipt({ hash });
453
566
  const { address: routerAddress } = getRouter2(chainId);
454
567
  const [block, tx] = await Promise.all([
455
568
  provider.getBlock({ blockNumber: receipt.blockNumber }),
456
569
  provider.getTransaction({ hash })
457
570
  ]);
458
571
  const yodlEvent = decodeYodlFromLogs(receipt.logs, routerAddress);
459
- const swapEvent = decodeSwapFromLogs(receipt.logs);
572
+ const swapLogs = yodlEvent ? receipt.logs.filter((l) => (l.logIndex ?? 0) < yodlEvent.logIndex) : receipt.logs;
573
+ const swapEvent = decodeSwapFromLogs(swapLogs);
460
574
  if (yodlEvent) {
461
575
  const blockTimestamp = toBlockTimestamp(block);
462
576
  const webhooks = extractEmbeddedParams(tx.input);
@@ -474,11 +588,23 @@ async function decodePayment(hash, chainId, clients) {
474
588
  }
475
589
 
476
590
  // src/yodl-payment.ts
477
- import { getTokenByAddress as getTokenByAddress2 } from "@yodlpay/tokenlists";
478
- import { formatUnits, zeroAddress } from "viem";
479
- function buildTokenInfo(params) {
480
- const inToken = getTokenByAddress2(params.tokenIn, params.inChainId);
481
- const outToken = getTokenByAddress2(params.tokenOut, params.outChainId);
591
+ import {
592
+ formatUnits,
593
+ zeroAddress
594
+ } from "viem";
595
+ async function buildTokenInfo(params, clients) {
596
+ const [inToken, outToken] = await Promise.all([
597
+ resolveToken(
598
+ params.tokenIn,
599
+ params.inChainId,
600
+ getClient(clients, params.inChainId)
601
+ ),
602
+ resolveToken(
603
+ params.tokenOut,
604
+ params.outChainId,
605
+ getClient(clients, params.outChainId)
606
+ )
607
+ ]);
482
608
  return {
483
609
  tokenIn: {
484
610
  ...inToken,
@@ -500,19 +626,22 @@ function buildTokenInfo(params) {
500
626
  }
501
627
  };
502
628
  }
503
- function extractTokenInfo(paymentInfo, chainId, txHash) {
629
+ async function extractTokenInfo(paymentInfo, chainId, txHash, clients) {
504
630
  switch (paymentInfo.type) {
505
631
  case "direct": {
506
632
  return {
507
- ...buildTokenInfo({
508
- tokenIn: paymentInfo.token,
509
- tokenInAmount: paymentInfo.amount,
510
- tokenOut: paymentInfo.token,
511
- tokenOutAmountGross: paymentInfo.amount,
512
- tokenOutAmountNet: paymentInfo.amount,
513
- inChainId: chainId,
514
- outChainId: chainId
515
- }),
633
+ ...await buildTokenInfo(
634
+ {
635
+ tokenIn: paymentInfo.token,
636
+ tokenInAmount: paymentInfo.amount,
637
+ tokenOut: paymentInfo.token,
638
+ tokenOutAmountGross: paymentInfo.amount,
639
+ tokenOutAmountNet: paymentInfo.amount,
640
+ inChainId: chainId,
641
+ outChainId: chainId
642
+ },
643
+ clients
644
+ ),
516
645
  sourceChainId: chainId,
517
646
  sourceTxHash: txHash,
518
647
  destinationChainId: chainId,
@@ -522,15 +651,18 @@ function extractTokenInfo(paymentInfo, chainId, txHash) {
522
651
  }
523
652
  case "swap": {
524
653
  return {
525
- ...buildTokenInfo({
526
- tokenIn: paymentInfo.tokenIn,
527
- tokenInAmount: paymentInfo.tokenInAmount,
528
- tokenOut: paymentInfo.token,
529
- tokenOutAmountGross: paymentInfo.tokenOutAmount,
530
- tokenOutAmountNet: paymentInfo.amount,
531
- inChainId: chainId,
532
- outChainId: chainId
533
- }),
654
+ ...await buildTokenInfo(
655
+ {
656
+ tokenIn: paymentInfo.tokenIn,
657
+ tokenInAmount: paymentInfo.tokenInAmount,
658
+ tokenOut: paymentInfo.token,
659
+ tokenOutAmountGross: paymentInfo.tokenOutAmount,
660
+ tokenOutAmountNet: paymentInfo.amount,
661
+ inChainId: chainId,
662
+ outChainId: chainId
663
+ },
664
+ clients
665
+ ),
534
666
  sourceChainId: chainId,
535
667
  sourceTxHash: txHash,
536
668
  destinationChainId: chainId,
@@ -540,15 +672,18 @@ function extractTokenInfo(paymentInfo, chainId, txHash) {
540
672
  }
541
673
  case "bridge": {
542
674
  return {
543
- ...buildTokenInfo({
544
- tokenIn: paymentInfo.tokenIn,
545
- tokenInAmount: paymentInfo.tokenInAmount,
546
- tokenOut: paymentInfo.tokenOut,
547
- tokenOutAmountGross: paymentInfo.tokenOutAmountGross,
548
- tokenOutAmountNet: paymentInfo.amount,
549
- inChainId: paymentInfo.sourceChainId,
550
- outChainId: paymentInfo.destinationChainId
551
- }),
675
+ ...await buildTokenInfo(
676
+ {
677
+ tokenIn: paymentInfo.tokenIn,
678
+ tokenInAmount: paymentInfo.tokenInAmount,
679
+ tokenOut: paymentInfo.tokenOut,
680
+ tokenOutAmountGross: paymentInfo.tokenOutAmountGross,
681
+ tokenOutAmountNet: paymentInfo.amount,
682
+ inChainId: paymentInfo.sourceChainId,
683
+ outChainId: paymentInfo.destinationChainId
684
+ },
685
+ clients
686
+ ),
552
687
  sourceChainId: paymentInfo.sourceChainId,
553
688
  sourceTxHash: paymentInfo.sourceTxHash,
554
689
  destinationChainId: paymentInfo.destinationChainId,
@@ -558,12 +693,22 @@ function extractTokenInfo(paymentInfo, chainId, txHash) {
558
693
  }
559
694
  }
560
695
  }
561
- async function decodeYodlPayment(txHash, chainId, clients) {
562
- const paymentInfo = await decodePayment(txHash, chainId, clients);
696
+ async function decodeYodlPayment(txHash, chainId, clients, cachedReceipt) {
697
+ const paymentInfo = await decodePayment(
698
+ txHash,
699
+ chainId,
700
+ clients,
701
+ cachedReceipt
702
+ );
563
703
  const firstWebhook = paymentInfo.webhooks[0];
564
704
  const processorAddress = firstWebhook?.webhookAddress ?? zeroAddress;
565
705
  const processorMemo = firstWebhook?.memo ?? "";
566
- const tokenInfo = extractTokenInfo(paymentInfo, chainId, txHash);
706
+ const tokenInfo = await extractTokenInfo(
707
+ paymentInfo,
708
+ chainId,
709
+ txHash,
710
+ clients
711
+ );
567
712
  return {
568
713
  senderAddress: paymentInfo.sender,
569
714
  receiverAddress: paymentInfo.receiver,
@@ -582,5 +727,6 @@ export {
582
727
  decodeBridgePayment,
583
728
  decodePayment,
584
729
  decodeYodlFromLogs,
585
- decodeYodlPayment
730
+ decodeYodlPayment,
731
+ detectChain
586
732
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yodlpay/payment-decoder",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "Decode Yodl payment hashes into structured payment data",
5
5
  "keywords": [
6
6
  "yodl",
@@ -38,8 +38,8 @@
38
38
  "prepublishOnly": "bun run build"
39
39
  },
40
40
  "devDependencies": {
41
- "@biomejs/biome": "^2.3.14",
42
- "@relayprotocol/relay-sdk": "^5.1.0",
41
+ "@biomejs/biome": "^2.4.4",
42
+ "@relayprotocol/relay-sdk": "^5.2.0",
43
43
  "@semantic-release/changelog": "^6.0.3",
44
44
  "@semantic-release/git": "^10.0.1",
45
45
  "@types/bun": "latest",
@@ -48,7 +48,7 @@
48
48
  "zod": "^4.3.6"
49
49
  },
50
50
  "dependencies": {
51
- "@yodlpay/tokenlists": "^1.1.7"
51
+ "@yodlpay/tokenlists": "^1.1.12"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "typescript": "^5",