@yodlpay/payment-decoder 1.3.4 → 1.3.5
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.d.ts +6 -4
- package/dist/index.js +220 -276
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -84,6 +84,8 @@ interface DecodeBridgeOptions {
|
|
|
84
84
|
includeAcross?: boolean;
|
|
85
85
|
/** Receipt of the fill (destination) tx, used for Across fill-side lookups. */
|
|
86
86
|
fillReceipt?: TransactionReceipt;
|
|
87
|
+
/** Receipt of the source tx, used to skip re-fetching when hash is the source. */
|
|
88
|
+
sourceReceipt?: TransactionReceipt;
|
|
87
89
|
}
|
|
88
90
|
declare function decodeBridgePayment(hash: Hex, clients: ChainClients, options?: DecodeBridgeOptions): Promise<Extract<PaymentInfo, {
|
|
89
91
|
type: "bridge";
|
|
@@ -107,6 +109,10 @@ interface DecodedYodlEvent {
|
|
|
107
109
|
*/
|
|
108
110
|
declare function decodeYodlFromLogs(logs: readonly Log[], routerAddress: Address): DecodedYodlEvent | undefined;
|
|
109
111
|
|
|
112
|
+
declare function decodePayment(hash: Hex, chainId: number, clients: ChainClients, cachedReceipt?: TransactionReceipt): Promise<PaymentInfo>;
|
|
113
|
+
|
|
114
|
+
declare function decodeYodlPayment(txHash: Hex, chainId: number, clients: ChainClients, cachedReceipt?: TransactionReceipt): Promise<YodlPayment>;
|
|
115
|
+
|
|
110
116
|
declare class NoBridgeFoundError extends Error {
|
|
111
117
|
constructor(message?: string);
|
|
112
118
|
}
|
|
@@ -114,8 +120,4 @@ declare class NoYodlEventError extends Error {
|
|
|
114
120
|
constructor(message?: string);
|
|
115
121
|
}
|
|
116
122
|
|
|
117
|
-
declare function decodePayment(hash: Hex, chainId: number, clients: ChainClients, cachedReceipt?: TransactionReceipt): Promise<PaymentInfo>;
|
|
118
|
-
|
|
119
|
-
declare function decodeYodlPayment(txHash: Hex, chainId: number, clients: ChainClients, cachedReceipt?: TransactionReceipt): Promise<YodlPayment>;
|
|
120
|
-
|
|
121
123
|
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,18 +1,4 @@
|
|
|
1
|
-
// src/across-bridge.ts
|
|
2
|
-
import { parseDepositLogs, parseFillLogs } from "@across-protocol/app-sdk";
|
|
3
|
-
import { getRouter as getRouter2 } from "@yodlpay/tokenlists";
|
|
4
|
-
|
|
5
|
-
// src/across-client.ts
|
|
6
|
-
import { isHash } from "viem";
|
|
7
|
-
import { z } from "zod";
|
|
8
|
-
|
|
9
1
|
// src/errors.ts
|
|
10
|
-
import {
|
|
11
|
-
AbiDecodingDataSizeTooSmallError,
|
|
12
|
-
AbiEventSignatureEmptyTopicsError,
|
|
13
|
-
AbiEventSignatureNotFoundError,
|
|
14
|
-
DecodeLogTopicsMismatch
|
|
15
|
-
} from "viem";
|
|
16
2
|
var ExpectedDecodeError = class extends Error {
|
|
17
3
|
constructor(message = "Expected decode error") {
|
|
18
4
|
super(message);
|
|
@@ -31,141 +17,37 @@ var NoYodlEventError = class extends Error {
|
|
|
31
17
|
this.name = "NoYodlEventError";
|
|
32
18
|
}
|
|
33
19
|
};
|
|
34
|
-
function isExpectedDecodeError(error) {
|
|
35
|
-
return error instanceof AbiEventSignatureNotFoundError || error instanceof AbiEventSignatureEmptyTopicsError || error instanceof AbiDecodingDataSizeTooSmallError || error instanceof DecodeLogTopicsMismatch;
|
|
36
|
-
}
|
|
37
20
|
|
|
38
|
-
// src/across-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
var chainId = z.union([
|
|
42
|
-
z.number().int().positive(),
|
|
43
|
-
z.string().regex(/^\d+$/).transform(Number)
|
|
44
|
-
]).pipe(z.number().int().positive());
|
|
45
|
-
var txHash = z.string().refine((v) => isHash(v));
|
|
46
|
-
var nullableTxHash = txHash.nullish().transform((v) => v ?? null);
|
|
47
|
-
var depositId = z.union([
|
|
48
|
-
z.string().regex(/^\d+$/),
|
|
49
|
-
z.number().int().nonnegative().transform((v) => String(v))
|
|
50
|
-
]);
|
|
51
|
-
var AcrossDepositStatusSchema = z.object({
|
|
52
|
-
status: z.enum(["filled", "pending", "expired", "refunded"]),
|
|
53
|
-
originChainId: chainId,
|
|
54
|
-
destinationChainId: chainId,
|
|
55
|
-
depositId,
|
|
56
|
-
depositTxnRef: txHash.optional(),
|
|
57
|
-
depositTxHash: txHash.optional(),
|
|
58
|
-
fillTxnRef: nullableTxHash,
|
|
59
|
-
fillTx: nullableTxHash,
|
|
60
|
-
actionsSucceeded: z.boolean().nullish().transform((v) => v ?? null)
|
|
61
|
-
}).transform((d) => {
|
|
62
|
-
const depositTxnRef = d.depositTxnRef ?? d.depositTxHash;
|
|
63
|
-
if (!depositTxnRef) {
|
|
64
|
-
throw new Error("Missing depositTxnRef and depositTxHash");
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
status: d.status,
|
|
68
|
-
originChainId: d.originChainId,
|
|
69
|
-
destinationChainId: d.destinationChainId,
|
|
70
|
-
depositId: d.depositId,
|
|
71
|
-
depositTxnRef,
|
|
72
|
-
fillTxnRef: d.fillTxnRef ?? d.fillTx,
|
|
73
|
-
actionsSucceeded: d.actionsSucceeded
|
|
74
|
-
};
|
|
75
|
-
});
|
|
76
|
-
async function fetchAcrossDepositByDepositId(originChainId, depositId2) {
|
|
77
|
-
const url = new URL(ACROSS_API);
|
|
78
|
-
url.searchParams.set("originChainId", String(originChainId));
|
|
79
|
-
url.searchParams.set("depositId", depositId2);
|
|
80
|
-
return fetchAcrossDeposit(url);
|
|
81
|
-
}
|
|
82
|
-
async function fetchAcrossDeposit(url) {
|
|
83
|
-
const controller = new AbortController();
|
|
84
|
-
const timeout = setTimeout(() => controller.abort(), ACROSS_TIMEOUT_MS);
|
|
85
|
-
let response;
|
|
86
|
-
try {
|
|
87
|
-
response = await fetch(url, { signal: controller.signal });
|
|
88
|
-
} catch (error) {
|
|
89
|
-
const message = error instanceof Error && error.name === "AbortError" ? `Across API request timed out after ${ACROSS_TIMEOUT_MS}ms` : "Across API request failed";
|
|
90
|
-
throw new Error(message, { cause: error });
|
|
91
|
-
} finally {
|
|
92
|
-
clearTimeout(timeout);
|
|
93
|
-
}
|
|
94
|
-
if (response.status === 404) throw new NoBridgeFoundError();
|
|
95
|
-
if (!response.ok) {
|
|
96
|
-
const body = await response.text().catch(() => "");
|
|
97
|
-
const kind = response.status === 429 || response.status >= 500 ? "temporary error" : "error";
|
|
98
|
-
throw new Error(
|
|
99
|
-
`Across API ${kind} (${response.status})${body ? ` - ${body}` : ""}`
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
const data = await response.json().catch(() => {
|
|
103
|
-
throw new Error("Across API returned invalid JSON");
|
|
104
|
-
});
|
|
105
|
-
const result = AcrossDepositStatusSchema.safeParse(data);
|
|
106
|
-
if (!result.success) {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`Across API response validation failed: ${result.error.message}`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
return result.data;
|
|
112
|
-
}
|
|
113
|
-
async function fetchAcrossDepositByTx(hash) {
|
|
114
|
-
const url = new URL(ACROSS_API);
|
|
115
|
-
url.searchParams.set("depositTxnRef", hash);
|
|
116
|
-
return fetchAcrossDeposit(url);
|
|
117
|
-
}
|
|
21
|
+
// src/bridges/across-bridge.ts
|
|
22
|
+
import { parseDepositLogs, parseFillLogs } from "@across-protocol/app-sdk";
|
|
23
|
+
import { getRouter as getRouter2 } from "@yodlpay/tokenlists";
|
|
118
24
|
|
|
119
|
-
// src/decode-utils.ts
|
|
25
|
+
// src/core/decode-utils.ts
|
|
120
26
|
import { tokenlist } from "@yodlpay/tokenlists";
|
|
121
27
|
import {
|
|
122
|
-
decodeEventLog,
|
|
123
28
|
erc20Abi as erc20Abi2,
|
|
124
29
|
getAddress,
|
|
125
|
-
isAddressEqual
|
|
30
|
+
isAddressEqual,
|
|
31
|
+
parseEventLogs
|
|
126
32
|
} from "viem";
|
|
127
33
|
|
|
128
34
|
// src/abi.ts
|
|
129
35
|
import { getRouterAbi } from "@yodlpay/tokenlists";
|
|
36
|
+
import { parseAbi } from "viem";
|
|
130
37
|
var yodlAbi = getRouterAbi("0.8");
|
|
131
|
-
var relaySwapAbi = [
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
name: "Swap",
|
|
135
|
-
inputs: [
|
|
136
|
-
{ name: "sender", type: "address", indexed: true },
|
|
137
|
-
{ name: "recipient", type: "address", indexed: true },
|
|
138
|
-
{ name: "inputToken", type: "address", indexed: false },
|
|
139
|
-
{ name: "outputToken", type: "address", indexed: false },
|
|
140
|
-
{ name: "inputAmount", type: "uint256", indexed: false },
|
|
141
|
-
{ name: "outputAmount", type: "uint256", indexed: false }
|
|
142
|
-
]
|
|
143
|
-
}
|
|
144
|
-
];
|
|
145
|
-
var entryPointAbi = [
|
|
146
|
-
{
|
|
147
|
-
type: "event",
|
|
148
|
-
name: "UserOperationEvent",
|
|
149
|
-
inputs: [
|
|
150
|
-
{ name: "userOpHash", type: "bytes32", indexed: true },
|
|
151
|
-
{ name: "sender", type: "address", indexed: true },
|
|
152
|
-
{ name: "paymaster", type: "address", indexed: true },
|
|
153
|
-
{ name: "nonce", type: "uint256", indexed: false },
|
|
154
|
-
{ name: "success", type: "bool", indexed: false },
|
|
155
|
-
{ name: "actualGasCost", type: "uint256", indexed: false },
|
|
156
|
-
{ name: "actualGasUsed", type: "uint256", indexed: false }
|
|
157
|
-
]
|
|
158
|
-
}
|
|
159
|
-
];
|
|
38
|
+
var relaySwapAbi = parseAbi([
|
|
39
|
+
"event Swap(address indexed sender, address indexed recipient, address inputToken, address outputToken, uint256 inputAmount, uint256 outputAmount)"
|
|
40
|
+
]);
|
|
160
41
|
|
|
161
42
|
// src/utils.ts
|
|
162
43
|
import { getTokenByAddress } from "@yodlpay/tokenlists";
|
|
163
44
|
import {
|
|
164
45
|
erc20Abi,
|
|
165
46
|
getContract,
|
|
166
|
-
hexToString
|
|
47
|
+
hexToString,
|
|
48
|
+
stringify
|
|
167
49
|
} from "viem";
|
|
168
|
-
import { z
|
|
50
|
+
import { z } from "zod";
|
|
169
51
|
function decodeMemo(memo) {
|
|
170
52
|
if (!memo || memo === "0x") return "";
|
|
171
53
|
try {
|
|
@@ -174,8 +56,8 @@ function decodeMemo(memo) {
|
|
|
174
56
|
return "";
|
|
175
57
|
}
|
|
176
58
|
}
|
|
177
|
-
var erc20String =
|
|
178
|
-
var erc20Decimals =
|
|
59
|
+
var erc20String = z.string().max(64).transform((s) => s.replace(/\p{Cc}/gu, ""));
|
|
60
|
+
var erc20Decimals = z.number().int().nonnegative().max(36);
|
|
179
61
|
async function resolveToken(address, chainId2, client) {
|
|
180
62
|
try {
|
|
181
63
|
return getTokenByAddress(address, chainId2);
|
|
@@ -196,98 +78,56 @@ async function resolveToken(address, chainId2, client) {
|
|
|
196
78
|
}
|
|
197
79
|
}
|
|
198
80
|
|
|
199
|
-
// src/decode-utils.ts
|
|
200
|
-
function* matchingEvents(logs, options) {
|
|
201
|
-
const { abi, eventName, address, context } = options;
|
|
202
|
-
for (const log of logs) {
|
|
203
|
-
if (address && !isAddressEqual(log.address, address)) {
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
try {
|
|
207
|
-
const decoded = decodeEventLog({
|
|
208
|
-
abi,
|
|
209
|
-
data: log.data,
|
|
210
|
-
topics: log.topics
|
|
211
|
-
});
|
|
212
|
-
if (decoded.eventName === eventName) {
|
|
213
|
-
yield { decoded, log };
|
|
214
|
-
}
|
|
215
|
-
} catch (error) {
|
|
216
|
-
if (!isExpectedDecodeError(error)) {
|
|
217
|
-
const contextStr = context ? ` (${context})` : "";
|
|
218
|
-
console.warn(
|
|
219
|
-
`[payment-decoder] Unexpected error decoding ${eventName}${contextStr}:`,
|
|
220
|
-
error
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
function findEventInLogs(logs, options, transform) {
|
|
227
|
-
for (const { decoded, log } of matchingEvents(logs, options)) {
|
|
228
|
-
return transform(decoded, log);
|
|
229
|
-
}
|
|
230
|
-
return void 0;
|
|
231
|
-
}
|
|
232
|
-
function collectEventsFromLogs(logs, options, transform) {
|
|
233
|
-
const results = [];
|
|
234
|
-
for (const { decoded, log } of matchingEvents(logs, options)) {
|
|
235
|
-
results.push(transform(decoded, log));
|
|
236
|
-
}
|
|
237
|
-
return results;
|
|
238
|
-
}
|
|
81
|
+
// src/core/decode-utils.ts
|
|
239
82
|
function decodeYodlFromLogs(logs, routerAddress) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
);
|
|
83
|
+
const parsed = parseEventLogs({
|
|
84
|
+
abi: yodlAbi,
|
|
85
|
+
logs: logs.filter((l) => isAddressEqual(l.address, routerAddress)),
|
|
86
|
+
eventName: "Yodl",
|
|
87
|
+
strict: true
|
|
88
|
+
});
|
|
89
|
+
const first = parsed[0];
|
|
90
|
+
if (!first) return void 0;
|
|
91
|
+
const args = first.args;
|
|
92
|
+
return {
|
|
93
|
+
sender: args.sender,
|
|
94
|
+
receiver: args.receiver,
|
|
95
|
+
token: args.token,
|
|
96
|
+
amount: args.amount,
|
|
97
|
+
memo: decodeMemo(args.memo),
|
|
98
|
+
logIndex: first.logIndex ?? 0
|
|
99
|
+
};
|
|
260
100
|
}
|
|
261
101
|
function decodeSwapFromLogs(logs) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
102
|
+
const parsed = parseEventLogs({
|
|
103
|
+
abi: relaySwapAbi,
|
|
104
|
+
logs: [...logs],
|
|
105
|
+
eventName: "Swap",
|
|
106
|
+
strict: true
|
|
107
|
+
});
|
|
108
|
+
const first = parsed[0];
|
|
109
|
+
if (!first) return void 0;
|
|
110
|
+
return {
|
|
111
|
+
tokenIn: first.args.inputToken,
|
|
112
|
+
tokenOut: first.args.outputToken,
|
|
113
|
+
tokenInAmount: first.args.inputAmount,
|
|
114
|
+
tokenOutAmount: first.args.outputAmount,
|
|
115
|
+
service: "relay"
|
|
116
|
+
};
|
|
276
117
|
}
|
|
277
118
|
function extractTokenTransfers(logs) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
);
|
|
119
|
+
const parsed = parseEventLogs({
|
|
120
|
+
abi: erc20Abi2,
|
|
121
|
+
logs: [...logs],
|
|
122
|
+
eventName: "Transfer",
|
|
123
|
+
strict: false
|
|
124
|
+
});
|
|
125
|
+
return parsed.flatMap((log) => {
|
|
126
|
+
if (!log.args) return [];
|
|
127
|
+
const { from, to, value } = log.args;
|
|
128
|
+
if (!from || !to || value === void 0) return [];
|
|
129
|
+
return [{ token: log.address, from, to, amount: value }];
|
|
130
|
+
});
|
|
291
131
|
}
|
|
292
132
|
function isKnownToken(address) {
|
|
293
133
|
return tokenlist.some((t) => isAddressEqual(t.address, address));
|
|
@@ -418,12 +258,12 @@ function buildPaymentEvent(yodlEvent, webhooks, blockTimestamp, senderOverride)
|
|
|
418
258
|
};
|
|
419
259
|
}
|
|
420
260
|
|
|
421
|
-
// src/embedded-params.ts
|
|
261
|
+
// src/core/embedded-params.ts
|
|
422
262
|
import { decodeFunctionData, toFunctionSelector } from "viem";
|
|
423
263
|
|
|
424
264
|
// src/validation.ts
|
|
425
|
-
import { isAddress, isHex } from "viem";
|
|
426
|
-
import { z as
|
|
265
|
+
import { isAddress, isHash, isHex } from "viem";
|
|
266
|
+
import { z as z2 } from "zod";
|
|
427
267
|
|
|
428
268
|
// src/chains.ts
|
|
429
269
|
import { chains as allChains } from "@yodlpay/tokenlists";
|
|
@@ -431,25 +271,25 @@ var chains = allChains.filter((c) => !c.testnet);
|
|
|
431
271
|
|
|
432
272
|
// src/validation.ts
|
|
433
273
|
var validChainIds = chains.map((c) => c.id);
|
|
434
|
-
var txHashSchema =
|
|
435
|
-
var chainIdSchema =
|
|
274
|
+
var txHashSchema = z2.string().refine((val) => isHash(val), "Invalid transaction hash");
|
|
275
|
+
var chainIdSchema = z2.coerce.number().int().refine((id) => validChainIds.includes(id), {
|
|
436
276
|
message: `Chain ID must be one of: ${validChainIds.join(", ")}`
|
|
437
277
|
});
|
|
438
|
-
var ArgsSchema =
|
|
439
|
-
|
|
440
|
-
|
|
278
|
+
var ArgsSchema = z2.union([
|
|
279
|
+
z2.tuple([txHashSchema, chainIdSchema]),
|
|
280
|
+
z2.tuple([txHashSchema])
|
|
441
281
|
]);
|
|
442
|
-
var WebhooksSchema =
|
|
443
|
-
|
|
444
|
-
webhookAddress:
|
|
445
|
-
payload:
|
|
282
|
+
var WebhooksSchema = z2.array(
|
|
283
|
+
z2.object({
|
|
284
|
+
webhookAddress: z2.string().refine((val) => isAddress(val)),
|
|
285
|
+
payload: z2.array(z2.string().refine((val) => isHex(val)))
|
|
446
286
|
}).transform((webhook) => ({
|
|
447
287
|
...webhook,
|
|
448
288
|
memo: webhook.payload[0] ? decodeMemo(webhook.payload[0]) : ""
|
|
449
289
|
}))
|
|
450
290
|
).catch([]);
|
|
451
291
|
|
|
452
|
-
// src/embedded-params.ts
|
|
292
|
+
// src/core/embedded-params.ts
|
|
453
293
|
function getYodlSelector() {
|
|
454
294
|
const yodlFunction = yodlAbi.find(
|
|
455
295
|
(i) => i.type === "function" && i.name === "yodlWithToken"
|
|
@@ -479,13 +319,96 @@ function extractEmbeddedParams(data) {
|
|
|
479
319
|
return [];
|
|
480
320
|
}
|
|
481
321
|
|
|
482
|
-
// src/
|
|
322
|
+
// src/bridges/across-client.ts
|
|
323
|
+
import { isHash as isHash2 } from "viem";
|
|
324
|
+
import { z as z3 } from "zod";
|
|
325
|
+
var ACROSS_API = "https://app.across.to/api/deposit/status";
|
|
326
|
+
var ACROSS_TIMEOUT_MS = 8e3;
|
|
327
|
+
var chainId = z3.union([
|
|
328
|
+
z3.number().int().positive(),
|
|
329
|
+
z3.string().regex(/^\d+$/).transform(Number)
|
|
330
|
+
]).pipe(z3.number().int().positive());
|
|
331
|
+
var txHash = z3.string().refine((v) => isHash2(v));
|
|
332
|
+
var nullableTxHash = txHash.nullish().transform((v) => v ?? null);
|
|
333
|
+
var depositId = z3.union([
|
|
334
|
+
z3.string().regex(/^\d+$/),
|
|
335
|
+
z3.number().int().nonnegative().transform((v) => String(v))
|
|
336
|
+
]);
|
|
337
|
+
var AcrossDepositStatusSchema = z3.object({
|
|
338
|
+
status: z3.enum(["filled", "pending", "expired", "refunded"]),
|
|
339
|
+
originChainId: chainId,
|
|
340
|
+
destinationChainId: chainId,
|
|
341
|
+
depositId,
|
|
342
|
+
depositTxnRef: txHash.optional(),
|
|
343
|
+
depositTxHash: txHash.optional(),
|
|
344
|
+
fillTxnRef: nullableTxHash,
|
|
345
|
+
fillTx: nullableTxHash,
|
|
346
|
+
actionsSucceeded: z3.boolean().nullish().transform((v) => v ?? null)
|
|
347
|
+
}).transform((d) => {
|
|
348
|
+
const depositTxnRef = d.depositTxnRef ?? d.depositTxHash;
|
|
349
|
+
if (!depositTxnRef) {
|
|
350
|
+
throw new Error("Missing depositTxnRef and depositTxHash");
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
status: d.status,
|
|
354
|
+
originChainId: d.originChainId,
|
|
355
|
+
destinationChainId: d.destinationChainId,
|
|
356
|
+
depositId: d.depositId,
|
|
357
|
+
depositTxnRef,
|
|
358
|
+
fillTxnRef: d.fillTxnRef ?? d.fillTx,
|
|
359
|
+
actionsSucceeded: d.actionsSucceeded
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
async function fetchAcrossDepositByDepositId(originChainId, depositId2) {
|
|
363
|
+
const url = new URL(ACROSS_API);
|
|
364
|
+
url.searchParams.set("originChainId", String(originChainId));
|
|
365
|
+
url.searchParams.set("depositId", depositId2);
|
|
366
|
+
return fetchAcrossDeposit(url);
|
|
367
|
+
}
|
|
368
|
+
async function fetchAcrossDeposit(url) {
|
|
369
|
+
const controller = new AbortController();
|
|
370
|
+
const timeout = setTimeout(() => controller.abort(), ACROSS_TIMEOUT_MS);
|
|
371
|
+
let response;
|
|
372
|
+
try {
|
|
373
|
+
response = await fetch(url, { signal: controller.signal });
|
|
374
|
+
} catch (error) {
|
|
375
|
+
const message = error instanceof Error && error.name === "AbortError" ? `Across API request timed out after ${ACROSS_TIMEOUT_MS}ms` : "Across API request failed";
|
|
376
|
+
throw new Error(message, { cause: error });
|
|
377
|
+
} finally {
|
|
378
|
+
clearTimeout(timeout);
|
|
379
|
+
}
|
|
380
|
+
if (response.status === 404) throw new NoBridgeFoundError();
|
|
381
|
+
if (!response.ok) {
|
|
382
|
+
const body = await response.text().catch(() => "");
|
|
383
|
+
const kind = response.status === 429 || response.status >= 500 ? "temporary error" : "error";
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Across API ${kind} (${response.status})${body ? ` - ${body}` : ""}`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
const data = await response.json().catch(() => {
|
|
389
|
+
throw new Error("Across API returned invalid JSON");
|
|
390
|
+
});
|
|
391
|
+
const result = AcrossDepositStatusSchema.safeParse(data);
|
|
392
|
+
if (!result.success) {
|
|
393
|
+
throw new Error(
|
|
394
|
+
`Across API response validation failed: ${result.error.message}`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
return result.data;
|
|
398
|
+
}
|
|
399
|
+
async function fetchAcrossDepositByTx(hash) {
|
|
400
|
+
const url = new URL(ACROSS_API);
|
|
401
|
+
url.searchParams.set("depositTxnRef", hash);
|
|
402
|
+
return fetchAcrossDeposit(url);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/bridges/relay-bridge.ts
|
|
483
406
|
import { getRouter } from "@yodlpay/tokenlists";
|
|
484
407
|
import {
|
|
485
|
-
|
|
486
|
-
|
|
408
|
+
isAddressEqual as isAddressEqual2,
|
|
409
|
+
parseEventLogs as parseEventLogs2
|
|
487
410
|
} from "viem";
|
|
488
|
-
import { entryPoint08Address } from "viem/account-abstraction";
|
|
411
|
+
import { entryPoint08Abi, entryPoint08Address } from "viem/account-abstraction";
|
|
489
412
|
|
|
490
413
|
// src/clients.ts
|
|
491
414
|
import {
|
|
@@ -503,7 +426,7 @@ function createClients() {
|
|
|
503
426
|
for (const chain of chains) {
|
|
504
427
|
clients[chain.id] = createPublicClient({
|
|
505
428
|
chain,
|
|
506
|
-
transport: http(rpcOverrides[chain.id])
|
|
429
|
+
transport: http(rpcOverrides[chain.id], { batch: true })
|
|
507
430
|
});
|
|
508
431
|
}
|
|
509
432
|
return clients;
|
|
@@ -533,7 +456,7 @@ async function detectChain(hash, clients) {
|
|
|
533
456
|
return found.value;
|
|
534
457
|
}
|
|
535
458
|
|
|
536
|
-
// src/relay-client.ts
|
|
459
|
+
// src/bridges/relay-client.ts
|
|
537
460
|
var RELAY_API = "https://api.relay.link";
|
|
538
461
|
async function getRelayRequests(params) {
|
|
539
462
|
const url = new URL(`${RELAY_API}/requests/v2`);
|
|
@@ -557,7 +480,7 @@ async function fetchRelayRequest(hash) {
|
|
|
557
480
|
return request;
|
|
558
481
|
}
|
|
559
482
|
|
|
560
|
-
// src/relay-bridge.ts
|
|
483
|
+
// src/bridges/relay-bridge.ts
|
|
561
484
|
async function calculateOutputAmountGross(inputAmount, inputToken, inputChainId, outputToken, outputChainId, clients) {
|
|
562
485
|
const [{ decimals: inputDecimals }, { decimals: outputDecimals }] = await Promise.all([
|
|
563
486
|
resolveToken(inputToken, inputChainId, getClient(clients, inputChainId)),
|
|
@@ -573,19 +496,15 @@ async function calculateOutputAmountGross(inputAmount, inputToken, inputChainId,
|
|
|
573
496
|
return inputAmount * 10n ** BigInt(-decimalDiff);
|
|
574
497
|
}
|
|
575
498
|
async function extractSenderFromSource(sourceReceipt, sourceProvider, sourceHash) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
return decoded.args.sender;
|
|
586
|
-
}
|
|
587
|
-
} catch {
|
|
588
|
-
}
|
|
499
|
+
const userOps = parseEventLogs2({
|
|
500
|
+
abi: entryPoint08Abi,
|
|
501
|
+
logs: sourceReceipt.logs.filter(
|
|
502
|
+
(l) => isAddressEqual2(l.address, entryPoint08Address)
|
|
503
|
+
),
|
|
504
|
+
eventName: "UserOperationEvent"
|
|
505
|
+
});
|
|
506
|
+
if (userOps[0]) {
|
|
507
|
+
return userOps[0].args.sender;
|
|
589
508
|
}
|
|
590
509
|
const sourceTx = await sourceProvider.getTransaction({ hash: sourceHash });
|
|
591
510
|
return sourceTx.from;
|
|
@@ -639,7 +558,7 @@ function parseRelayResponse(request) {
|
|
|
639
558
|
inputAmountGross: BigInt(data?.metadata?.currencyIn?.amount ?? 0)
|
|
640
559
|
};
|
|
641
560
|
}
|
|
642
|
-
async function decodeRelayBridgePayment(hash, clients) {
|
|
561
|
+
async function decodeRelayBridgePayment(hash, clients, options = {}) {
|
|
643
562
|
const request = await fetchRelayRequest(hash);
|
|
644
563
|
const {
|
|
645
564
|
sourceChainId,
|
|
@@ -654,9 +573,12 @@ async function decodeRelayBridgePayment(hash, clients) {
|
|
|
654
573
|
throw new NoBridgeFoundError();
|
|
655
574
|
}
|
|
656
575
|
const { address: routerAddress } = getRouter(destinationChainId);
|
|
576
|
+
const hashLower = hash.toLowerCase();
|
|
577
|
+
const cachedDestReceipt = options.fillReceipt && destinationTxHash.toLowerCase() === hashLower ? options.fillReceipt : void 0;
|
|
578
|
+
const cachedSourceReceipt = options.sourceReceipt && sourceTxHash.toLowerCase() === hashLower ? options.sourceReceipt : void 0;
|
|
657
579
|
const [destReceipt, sourceReceipt] = await Promise.all([
|
|
658
|
-
destProvider.getTransactionReceipt({ hash: destinationTxHash }),
|
|
659
|
-
sourceProvider.getTransactionReceipt({ hash: sourceTxHash })
|
|
580
|
+
cachedDestReceipt ?? destProvider.getTransactionReceipt({ hash: destinationTxHash }),
|
|
581
|
+
cachedSourceReceipt ?? sourceProvider.getTransactionReceipt({ hash: sourceTxHash })
|
|
660
582
|
]);
|
|
661
583
|
const yodlEvent = decodeYodlFromLogs(destReceipt.logs, routerAddress);
|
|
662
584
|
if (!yodlEvent) {
|
|
@@ -696,7 +618,7 @@ async function decodeRelayBridgePayment(hash, clients) {
|
|
|
696
618
|
return { type: "bridge", ...payment, ...bridge };
|
|
697
619
|
}
|
|
698
620
|
|
|
699
|
-
// src/across-bridge.ts
|
|
621
|
+
// src/bridges/across-bridge.ts
|
|
700
622
|
async function resolveAcrossStatus(hash, fillReceipt) {
|
|
701
623
|
try {
|
|
702
624
|
return await fetchAcrossDepositByTx(hash);
|
|
@@ -713,13 +635,13 @@ async function resolveAcrossStatus(hash, fillReceipt) {
|
|
|
713
635
|
Number(fillLog.originChainId),
|
|
714
636
|
String(fillLog.depositId)
|
|
715
637
|
);
|
|
716
|
-
if (status.fillTxnRef !== hash) {
|
|
638
|
+
if (!status.fillTxnRef || status.fillTxnRef.toLowerCase() !== hash.toLowerCase()) {
|
|
717
639
|
throw new NoBridgeFoundError();
|
|
718
640
|
}
|
|
719
641
|
return status;
|
|
720
642
|
}
|
|
721
|
-
async function decodeAcrossBridgePayment(hash, clients,
|
|
722
|
-
const status = await resolveAcrossStatus(hash, fillReceipt);
|
|
643
|
+
async function decodeAcrossBridgePayment(hash, clients, options = {}) {
|
|
644
|
+
const status = await resolveAcrossStatus(hash, options.fillReceipt);
|
|
723
645
|
if (status.status !== "filled" || !status.fillTxnRef) {
|
|
724
646
|
throw new NoBridgeFoundError();
|
|
725
647
|
}
|
|
@@ -736,9 +658,12 @@ async function decodeAcrossBridgePayment(hash, clients, fillReceipt) {
|
|
|
736
658
|
throw new NoBridgeFoundError();
|
|
737
659
|
}
|
|
738
660
|
const { address: routerAddress } = getRouter2(destinationChainId);
|
|
661
|
+
const hashLower = hash.toLowerCase();
|
|
662
|
+
const cachedDestReceipt = options.fillReceipt && destinationTxHash.toLowerCase() === hashLower ? options.fillReceipt : void 0;
|
|
663
|
+
const cachedSourceReceipt = options.sourceReceipt && sourceTxHash.toLowerCase() === hashLower ? options.sourceReceipt : void 0;
|
|
739
664
|
const [sourceReceipt, destReceipt] = await Promise.all([
|
|
740
|
-
sourceProvider.getTransactionReceipt({ hash: sourceTxHash }),
|
|
741
|
-
destProvider.getTransactionReceipt({ hash: destinationTxHash })
|
|
665
|
+
cachedSourceReceipt ?? sourceProvider.getTransactionReceipt({ hash: sourceTxHash }),
|
|
666
|
+
cachedDestReceipt ?? destProvider.getTransactionReceipt({ hash: destinationTxHash })
|
|
742
667
|
]);
|
|
743
668
|
const yodlEvent = decodeYodlFromLogs(destReceipt.logs, routerAddress);
|
|
744
669
|
if (!yodlEvent) {
|
|
@@ -805,12 +730,15 @@ async function decodeAcrossBridgePayment(hash, clients, fillReceipt) {
|
|
|
805
730
|
return { type: "bridge", ...payment, ...bridge };
|
|
806
731
|
}
|
|
807
732
|
|
|
808
|
-
// src/bridge-payment.ts
|
|
733
|
+
// src/bridges/bridge-payment.ts
|
|
809
734
|
async function decodeBridgePayment(hash, clients, options = {}) {
|
|
810
735
|
const includeAcross = options.includeAcross ?? true;
|
|
811
736
|
let relayError;
|
|
812
737
|
try {
|
|
813
|
-
return await decodeRelayBridgePayment(hash, clients
|
|
738
|
+
return await decodeRelayBridgePayment(hash, clients, {
|
|
739
|
+
fillReceipt: options.fillReceipt,
|
|
740
|
+
sourceReceipt: options.sourceReceipt
|
|
741
|
+
});
|
|
814
742
|
} catch (error) {
|
|
815
743
|
relayError = error;
|
|
816
744
|
if (!includeAcross) {
|
|
@@ -818,7 +746,10 @@ async function decodeBridgePayment(hash, clients, options = {}) {
|
|
|
818
746
|
}
|
|
819
747
|
}
|
|
820
748
|
try {
|
|
821
|
-
return await decodeAcrossBridgePayment(hash, clients,
|
|
749
|
+
return await decodeAcrossBridgePayment(hash, clients, {
|
|
750
|
+
fillReceipt: options.fillReceipt,
|
|
751
|
+
sourceReceipt: options.sourceReceipt
|
|
752
|
+
});
|
|
822
753
|
} catch (acrossError) {
|
|
823
754
|
if (!(relayError instanceof NoBridgeFoundError) && !(acrossError instanceof NoBridgeFoundError)) {
|
|
824
755
|
throw new Error("Both Relay and Across bridge decoders failed", {
|
|
@@ -835,7 +766,7 @@ async function decodeBridgePayment(hash, clients, options = {}) {
|
|
|
835
766
|
}
|
|
836
767
|
}
|
|
837
768
|
|
|
838
|
-
// src/payment-decoder.ts
|
|
769
|
+
// src/core/payment-decoder.ts
|
|
839
770
|
import { getRouter as getRouter3 } from "@yodlpay/tokenlists";
|
|
840
771
|
async function tryDecodeBridge(hash, clients, options = {}) {
|
|
841
772
|
try {
|
|
@@ -851,24 +782,35 @@ async function decodePayment(hash, chainId2, clients, cachedReceipt) {
|
|
|
851
782
|
const provider = getClient(clients, chainId2);
|
|
852
783
|
const receipt = cachedReceipt ?? await provider.getTransactionReceipt({ hash });
|
|
853
784
|
const { address: routerAddress } = getRouter3(chainId2);
|
|
854
|
-
const [block, tx] = await Promise.all([
|
|
855
|
-
provider.getBlock({ blockNumber: receipt.blockNumber }),
|
|
856
|
-
provider.getTransaction({ hash })
|
|
857
|
-
]);
|
|
858
785
|
const yodlEvent = decodeYodlFromLogs(receipt.logs, routerAddress);
|
|
859
786
|
const swapLogs = yodlEvent ? receipt.logs.filter((l) => (l.logIndex ?? 0) < yodlEvent.logIndex) : receipt.logs;
|
|
860
787
|
const swapEvent = decodeSwapFromLogs(swapLogs);
|
|
861
788
|
if (yodlEvent) {
|
|
862
|
-
const blockTimestamp = toBlockTimestamp(block);
|
|
863
|
-
const webhooks = extractEmbeddedParams(tx.input);
|
|
864
|
-
const paymentEvent = buildPaymentEvent(yodlEvent, webhooks, blockTimestamp);
|
|
865
789
|
if (swapEvent) {
|
|
866
|
-
|
|
790
|
+
const [block2, tx2] = await Promise.all([
|
|
791
|
+
provider.getBlock({ blockNumber: receipt.blockNumber }),
|
|
792
|
+
provider.getTransaction({ hash })
|
|
793
|
+
]);
|
|
794
|
+
const paymentEvent2 = buildPaymentEvent(
|
|
795
|
+
yodlEvent,
|
|
796
|
+
extractEmbeddedParams(tx2.input),
|
|
797
|
+
toBlockTimestamp(block2)
|
|
798
|
+
);
|
|
799
|
+
return { type: "swap", ...paymentEvent2, ...swapEvent };
|
|
867
800
|
}
|
|
868
801
|
const bridgeResult2 = await tryDecodeBridge(hash, clients, {
|
|
869
802
|
fillReceipt: receipt
|
|
870
803
|
});
|
|
871
804
|
if (bridgeResult2) return bridgeResult2;
|
|
805
|
+
const [block, tx] = await Promise.all([
|
|
806
|
+
provider.getBlock({ blockNumber: receipt.blockNumber }),
|
|
807
|
+
provider.getTransaction({ hash })
|
|
808
|
+
]);
|
|
809
|
+
const paymentEvent = buildPaymentEvent(
|
|
810
|
+
yodlEvent,
|
|
811
|
+
extractEmbeddedParams(tx.input),
|
|
812
|
+
toBlockTimestamp(block)
|
|
813
|
+
);
|
|
872
814
|
const inferredSwap = inferSwapFromTransfers(
|
|
873
815
|
swapLogs,
|
|
874
816
|
yodlEvent.sender,
|
|
@@ -879,12 +821,14 @@ async function decodePayment(hash, chainId2, clients, cachedReceipt) {
|
|
|
879
821
|
}
|
|
880
822
|
return { type: "direct", ...paymentEvent };
|
|
881
823
|
}
|
|
882
|
-
const bridgeResult = await tryDecodeBridge(hash, clients
|
|
824
|
+
const bridgeResult = await tryDecodeBridge(hash, clients, {
|
|
825
|
+
sourceReceipt: receipt
|
|
826
|
+
});
|
|
883
827
|
if (bridgeResult) return bridgeResult;
|
|
884
828
|
throw new NoYodlEventError();
|
|
885
829
|
}
|
|
886
830
|
|
|
887
|
-
// src/yodl-payment.ts
|
|
831
|
+
// src/core/yodl-payment.ts
|
|
888
832
|
import {
|
|
889
833
|
formatUnits,
|
|
890
834
|
zeroAddress
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yodlpay/payment-decoder",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
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.4.
|
|
42
|
-
"@relayprotocol/relay-sdk": "^5.2.
|
|
41
|
+
"@biomejs/biome": "^2.4.8",
|
|
42
|
+
"@relayprotocol/relay-sdk": "^5.2.1",
|
|
43
43
|
"@semantic-release/changelog": "^6.0.3",
|
|
44
44
|
"@semantic-release/git": "^10.0.1",
|
|
45
45
|
"@types/bun": "latest",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@across-protocol/app-sdk": "^0.5.0",
|
|
52
|
-
"@yodlpay/tokenlists": "^1.1.
|
|
52
|
+
"@yodlpay/tokenlists": "^1.1.13"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"typescript": "^5",
|