@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 +24 -3
- package/dist/index.d.ts +13 -4
- package/dist/index.js +221 -75
- package/package.json +4 -4
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>
|
|
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
|
-
**
|
|
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,
|
|
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 {
|
|
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 {
|
|
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:
|
|
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
|
|
196
|
-
(max, t) => t.amount > max
|
|
293
|
+
const maxAmount = fromSender.reduce(
|
|
294
|
+
(max, t) => t.amount > max ? t.amount : max,
|
|
295
|
+
0n
|
|
197
296
|
);
|
|
198
|
-
|
|
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
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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 =
|
|
230
|
-
|
|
231
|
-
webhookAddress:
|
|
232
|
-
payload:
|
|
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
|
|
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 } =
|
|
306
|
-
inputToken,
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
|
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 {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
42
|
-
"@relayprotocol/relay-sdk": "^5.
|
|
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.
|
|
51
|
+
"@yodlpay/tokenlists": "^1.1.12"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"typescript": "^5",
|