@yodlpay/payment-decoder 1.2.0 → 1.3.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/README.md +24 -3
- package/dist/index.d.ts +17 -8
- package/dist/index.js +72 -33
- package/package.json +1 -1
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 {
|
|
@@ -27,8 +35,8 @@ interface SwapInfo {
|
|
|
27
35
|
service: ServiceProvider;
|
|
28
36
|
}
|
|
29
37
|
interface BridgeInfo extends SwapInfo {
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
sourceChainId: number;
|
|
39
|
+
sourceTxHash: Hex;
|
|
32
40
|
destinationChainId: number;
|
|
33
41
|
destinationTxHash: Hex;
|
|
34
42
|
tokenOutAmountGross: bigint;
|
|
@@ -59,8 +67,8 @@ interface YodlPayment {
|
|
|
59
67
|
data: Record<string, unknown>;
|
|
60
68
|
tokenIn: TokenInInfo;
|
|
61
69
|
tokenOut: TokenOutInfo;
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
sourceChainId: number;
|
|
71
|
+
sourceTxHash: Hex;
|
|
64
72
|
destinationChainId: number;
|
|
65
73
|
destinationTxHash: Hex;
|
|
66
74
|
solver: string | null;
|
|
@@ -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,6 +23,25 @@ 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
|
|
21
47
|
import {
|
|
@@ -146,14 +172,15 @@ function decodeYodlFromLogs(logs, routerAddress) {
|
|
|
146
172
|
address: routerAddress,
|
|
147
173
|
context: "Yodl event"
|
|
148
174
|
},
|
|
149
|
-
(decoded) => {
|
|
175
|
+
(decoded, log) => {
|
|
150
176
|
const args = decoded.args;
|
|
151
177
|
return {
|
|
152
178
|
sender: args.sender,
|
|
153
179
|
receiver: args.receiver,
|
|
154
180
|
token: args.token,
|
|
155
181
|
amount: args.amount,
|
|
156
|
-
memo: decodeMemo(args.memo)
|
|
182
|
+
memo: decodeMemo(args.memo),
|
|
183
|
+
logIndex: log.logIndex ?? 0
|
|
157
184
|
};
|
|
158
185
|
}
|
|
159
186
|
);
|
|
@@ -220,11 +247,13 @@ import { chains as chains2 } from "@yodlpay/tokenlists";
|
|
|
220
247
|
import { isAddress, isHex } from "viem";
|
|
221
248
|
import { z } from "zod";
|
|
222
249
|
var validChainIds = chains2.map((c) => c.id);
|
|
223
|
-
var
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
250
|
+
var txHashSchema = z.string().regex(/^0x[a-fA-F0-9]{64}$/, "Invalid transaction hash").transform((val) => val);
|
|
251
|
+
var chainIdSchema = z.coerce.number().int().refine((id) => validChainIds.includes(id), {
|
|
252
|
+
message: `Chain ID must be one of: ${validChainIds.join(", ")}`
|
|
253
|
+
});
|
|
254
|
+
var ArgsSchema = z.union([
|
|
255
|
+
z.tuple([txHashSchema, chainIdSchema]),
|
|
256
|
+
z.tuple([txHashSchema])
|
|
228
257
|
]);
|
|
229
258
|
var WebhooksSchema = z.array(
|
|
230
259
|
z.object({
|
|
@@ -374,8 +403,8 @@ function parseRelayResponse(request) {
|
|
|
374
403
|
throw new NoBridgeFoundError();
|
|
375
404
|
}
|
|
376
405
|
return {
|
|
377
|
-
|
|
378
|
-
|
|
406
|
+
sourceChainId: inTx.chainId,
|
|
407
|
+
sourceTxHash: inTx.hash,
|
|
379
408
|
destinationChainId: outTx.chainId,
|
|
380
409
|
destinationTxHash: outTx.hash,
|
|
381
410
|
inputAmountGross: BigInt(data?.metadata?.currencyIn?.amount ?? 0)
|
|
@@ -384,18 +413,18 @@ function parseRelayResponse(request) {
|
|
|
384
413
|
async function decodeBridgePayment(hash, clients) {
|
|
385
414
|
const request = await fetchRelayRequest(hash);
|
|
386
415
|
const {
|
|
387
|
-
|
|
388
|
-
|
|
416
|
+
sourceChainId,
|
|
417
|
+
sourceTxHash,
|
|
389
418
|
destinationChainId,
|
|
390
419
|
destinationTxHash,
|
|
391
420
|
inputAmountGross
|
|
392
421
|
} = parseRelayResponse(request);
|
|
393
422
|
const destProvider = getClient(clients, destinationChainId);
|
|
394
|
-
const sourceProvider = getClient(clients,
|
|
423
|
+
const sourceProvider = getClient(clients, sourceChainId);
|
|
395
424
|
const { address: routerAddress } = getRouter(destinationChainId);
|
|
396
425
|
const [destReceipt, sourceReceipt] = await Promise.all([
|
|
397
426
|
destProvider.getTransactionReceipt({ hash: destinationTxHash }),
|
|
398
|
-
sourceProvider.getTransactionReceipt({ hash:
|
|
427
|
+
sourceProvider.getTransactionReceipt({ hash: sourceTxHash })
|
|
399
428
|
]);
|
|
400
429
|
const yodlEvent = decodeYodlFromLogs(destReceipt.logs, routerAddress);
|
|
401
430
|
if (!yodlEvent) {
|
|
@@ -406,7 +435,7 @@ async function decodeBridgePayment(hash, clients) {
|
|
|
406
435
|
const [destBlock, destTx, sender] = await Promise.all([
|
|
407
436
|
destProvider.getBlock({ blockNumber: destReceipt.blockNumber }),
|
|
408
437
|
destProvider.getTransaction({ hash: destinationTxHash }),
|
|
409
|
-
extractSenderFromSource(sourceReceipt, sourceProvider,
|
|
438
|
+
extractSenderFromSource(sourceReceipt, sourceProvider, sourceTxHash)
|
|
410
439
|
]);
|
|
411
440
|
const tokens = resolveBridgeTokens(
|
|
412
441
|
sourceReceipt,
|
|
@@ -414,12 +443,12 @@ async function decodeBridgePayment(hash, clients) {
|
|
|
414
443
|
yodlEvent,
|
|
415
444
|
sender,
|
|
416
445
|
inputAmountGross,
|
|
417
|
-
|
|
446
|
+
sourceChainId,
|
|
418
447
|
destinationChainId
|
|
419
448
|
);
|
|
420
449
|
const bridge = {
|
|
421
|
-
|
|
422
|
-
|
|
450
|
+
sourceChainId,
|
|
451
|
+
sourceTxHash,
|
|
423
452
|
destinationChainId,
|
|
424
453
|
destinationTxHash,
|
|
425
454
|
...tokens,
|
|
@@ -447,16 +476,17 @@ async function tryDecodeBridge(hash, clients) {
|
|
|
447
476
|
return void 0;
|
|
448
477
|
}
|
|
449
478
|
}
|
|
450
|
-
async function decodePayment(hash, chainId, clients) {
|
|
479
|
+
async function decodePayment(hash, chainId, clients, cachedReceipt) {
|
|
451
480
|
const provider = getClient(clients, chainId);
|
|
452
|
-
const receipt = await provider.getTransactionReceipt({ hash });
|
|
481
|
+
const receipt = cachedReceipt ?? await provider.getTransactionReceipt({ hash });
|
|
453
482
|
const { address: routerAddress } = getRouter2(chainId);
|
|
454
483
|
const [block, tx] = await Promise.all([
|
|
455
484
|
provider.getBlock({ blockNumber: receipt.blockNumber }),
|
|
456
485
|
provider.getTransaction({ hash })
|
|
457
486
|
]);
|
|
458
487
|
const yodlEvent = decodeYodlFromLogs(receipt.logs, routerAddress);
|
|
459
|
-
const
|
|
488
|
+
const swapLogs = yodlEvent ? receipt.logs.filter((l) => (l.logIndex ?? 0) < yodlEvent.logIndex) : receipt.logs;
|
|
489
|
+
const swapEvent = decodeSwapFromLogs(swapLogs);
|
|
460
490
|
if (yodlEvent) {
|
|
461
491
|
const blockTimestamp = toBlockTimestamp(block);
|
|
462
492
|
const webhooks = extractEmbeddedParams(tx.input);
|
|
@@ -475,7 +505,10 @@ async function decodePayment(hash, chainId, clients) {
|
|
|
475
505
|
|
|
476
506
|
// src/yodl-payment.ts
|
|
477
507
|
import { getTokenByAddress as getTokenByAddress2 } from "@yodlpay/tokenlists";
|
|
478
|
-
import {
|
|
508
|
+
import {
|
|
509
|
+
formatUnits,
|
|
510
|
+
zeroAddress
|
|
511
|
+
} from "viem";
|
|
479
512
|
function buildTokenInfo(params) {
|
|
480
513
|
const inToken = getTokenByAddress2(params.tokenIn, params.inChainId);
|
|
481
514
|
const outToken = getTokenByAddress2(params.tokenOut, params.outChainId);
|
|
@@ -513,8 +546,8 @@ function extractTokenInfo(paymentInfo, chainId, txHash) {
|
|
|
513
546
|
inChainId: chainId,
|
|
514
547
|
outChainId: chainId
|
|
515
548
|
}),
|
|
516
|
-
|
|
517
|
-
|
|
549
|
+
sourceChainId: chainId,
|
|
550
|
+
sourceTxHash: txHash,
|
|
518
551
|
destinationChainId: chainId,
|
|
519
552
|
destinationTxHash: txHash,
|
|
520
553
|
solver: null
|
|
@@ -531,8 +564,8 @@ function extractTokenInfo(paymentInfo, chainId, txHash) {
|
|
|
531
564
|
inChainId: chainId,
|
|
532
565
|
outChainId: chainId
|
|
533
566
|
}),
|
|
534
|
-
|
|
535
|
-
|
|
567
|
+
sourceChainId: chainId,
|
|
568
|
+
sourceTxHash: txHash,
|
|
536
569
|
destinationChainId: chainId,
|
|
537
570
|
destinationTxHash: txHash,
|
|
538
571
|
solver: paymentInfo.service ?? null
|
|
@@ -546,11 +579,11 @@ function extractTokenInfo(paymentInfo, chainId, txHash) {
|
|
|
546
579
|
tokenOut: paymentInfo.tokenOut,
|
|
547
580
|
tokenOutAmountGross: paymentInfo.tokenOutAmountGross,
|
|
548
581
|
tokenOutAmountNet: paymentInfo.amount,
|
|
549
|
-
inChainId: paymentInfo.
|
|
582
|
+
inChainId: paymentInfo.sourceChainId,
|
|
550
583
|
outChainId: paymentInfo.destinationChainId
|
|
551
584
|
}),
|
|
552
|
-
|
|
553
|
-
|
|
585
|
+
sourceChainId: paymentInfo.sourceChainId,
|
|
586
|
+
sourceTxHash: paymentInfo.sourceTxHash,
|
|
554
587
|
destinationChainId: paymentInfo.destinationChainId,
|
|
555
588
|
destinationTxHash: paymentInfo.destinationTxHash,
|
|
556
589
|
solver: paymentInfo.service ?? null
|
|
@@ -558,8 +591,13 @@ function extractTokenInfo(paymentInfo, chainId, txHash) {
|
|
|
558
591
|
}
|
|
559
592
|
}
|
|
560
593
|
}
|
|
561
|
-
async function decodeYodlPayment(txHash, chainId, clients) {
|
|
562
|
-
const paymentInfo = await decodePayment(
|
|
594
|
+
async function decodeYodlPayment(txHash, chainId, clients, cachedReceipt) {
|
|
595
|
+
const paymentInfo = await decodePayment(
|
|
596
|
+
txHash,
|
|
597
|
+
chainId,
|
|
598
|
+
clients,
|
|
599
|
+
cachedReceipt
|
|
600
|
+
);
|
|
563
601
|
const firstWebhook = paymentInfo.webhooks[0];
|
|
564
602
|
const processorAddress = firstWebhook?.webhookAddress ?? zeroAddress;
|
|
565
603
|
const processorMemo = firstWebhook?.memo ?? "";
|
|
@@ -582,5 +620,6 @@ export {
|
|
|
582
620
|
decodeBridgePayment,
|
|
583
621
|
decodePayment,
|
|
584
622
|
decodeYodlFromLogs,
|
|
585
|
-
decodeYodlPayment
|
|
623
|
+
decodeYodlPayment,
|
|
624
|
+
detectChain
|
|
586
625
|
};
|