11import { FiatToken , Networks } from "@vortexfi/shared" ;
2+ import logger from "../../../config/logger" ;
23import { SANDBOX_ENABLED } from "../../../constants/constants" ;
34import QuoteTicket from "../../../models/quoteTicket.model" ;
45import RampState from "../../../models/rampState.model" ;
@@ -11,6 +12,76 @@ enum TransactionHashKey {
1112
1213type ExplorerLinkBuilder = ( hash : string , rampState : RampState , quote : QuoteTicket ) => string ;
1314
15+ // Map chain names from AxelarScan to their respective explorer URLs
16+ const CHAIN_EXPLORERS : Record < string , string > = {
17+ arbitrum : "https://arbiscan.io/tx" ,
18+ avalanche : "https://snowtrace.io/tx" ,
19+ base : "https://basescan.org/tx" ,
20+ binance : "https://bscscan.com/tx" ,
21+ bsc : "https://bscscan.com/tx" ,
22+ ethereum : "https://etherscan.io/tx" ,
23+ moonbeam : "https://moonscan.io/tx" ,
24+ polygon : "https://polygonscan.com/tx"
25+ } ;
26+
27+ async function getAxelarScanExecutionLink ( hash : string ) : Promise < { explorerLink : string ; executionHash : string } > {
28+ const url = "https://api.axelarscan.io/gmp/searchGMP" ;
29+ const response = await fetch ( url , {
30+ body : JSON . stringify ( { txHash : hash } ) ,
31+ headers : {
32+ "Content-Type" : "application/json"
33+ } ,
34+ method : "POST"
35+ } ) ;
36+
37+ if ( ! response . ok ) {
38+ logger . error ( `Failed to fetch AxelarScan link for hash ${ hash } : ${ response . statusText } ` ) ;
39+ // Fallback to AxelarScan link
40+ return {
41+ executionHash : hash ,
42+ explorerLink : `https://axelarscan.io/gmp/${ hash } `
43+ } ;
44+ }
45+
46+ try {
47+ const data = ( await response . json ( ) ) . data ;
48+ const chain = data [ 0 ] ?. express_executed ?. chain || data [ 0 ] ?. executed ?. chain ;
49+ const executionHash = data [ 0 ] ?. express_executed ?. transactionHash || data [ 0 ] ?. executed ?. transactionHash ;
50+
51+ if ( ! executionHash ) {
52+ logger . warn ( `No execution hash found in AxelarScan response for ${ hash } ` ) ;
53+ return {
54+ executionHash : hash ,
55+ explorerLink : `https://axelarscan.io/gmp/${ hash } `
56+ } ;
57+ }
58+
59+ // Normalize chain name to lowercase for matching
60+ const normalizedChain = chain ?. toLowerCase ( ) ;
61+ const explorerBaseUrl = normalizedChain ? CHAIN_EXPLORERS [ normalizedChain ] : undefined ;
62+
63+ if ( explorerBaseUrl ) {
64+ return {
65+ executionHash,
66+ explorerLink : `${ explorerBaseUrl } /${ executionHash } `
67+ } ;
68+ }
69+
70+ // Fallback to AxelarScan if chain is not recognized
71+ logger . warn ( `Unknown chain "${ chain } " in AxelarScan response for hash ${ hash } , using AxelarScan link` ) ;
72+ return {
73+ executionHash,
74+ explorerLink : `https://axelarscan.io/gmp/${ executionHash } `
75+ } ;
76+ } catch ( error ) {
77+ logger . error ( `Failed to parse AxelarScan response for hash ${ hash } : ${ error } ` ) ;
78+ return {
79+ executionHash : hash ,
80+ explorerLink : `https://axelarscan.io/gmp/${ hash } `
81+ } ;
82+ }
83+ }
84+
1485const EXPLORER_LINK_BUILDERS : Record < TransactionHashKey , ExplorerLinkBuilder > = {
1586 [ TransactionHashKey . HydrationToAssethubXcmHash ] : hash => `https://hydration.subscan.io/block/${ hash } ` ,
1687
@@ -41,7 +112,10 @@ function deriveSandboxTransactionHash(rampState: RampState): string {
41112/// For now, this will be the hash of the last transaction on the second-last network, ie. the outgoing transfer
42113/// and not the incoming one.
43114/// Only works for ramping processes that have reached the "complete" phase.
44- export function getFinalTransactionHashForRamp ( rampState : RampState , quote : QuoteTicket ) {
115+ export async function getFinalTransactionHashForRamp (
116+ rampState : RampState ,
117+ quote : QuoteTicket
118+ ) : Promise < { transactionExplorerLink : string | undefined ; transactionHash : string | undefined } > {
45119 if ( rampState . currentPhase !== "complete" ) {
46120 return { transactionExplorerLink : undefined , transactionHash : undefined } ;
47121 }
@@ -56,7 +130,40 @@ export function getFinalTransactionHashForRamp(rampState: RampState, quote: Quot
56130
57131 for ( const hashKey of TRANSACTION_HASH_PRIORITY ) {
58132 const hash = rampState . state [ hashKey ] ;
133+
59134 if ( hash ) {
135+ // For SquidRouter swaps, query the execution hash from AxelarScan
136+ if ( hashKey === TransactionHashKey . SquidRouterSwapHash ) {
137+ try {
138+ const isMoneriumPolygonOnramp =
139+ rampState . from === "sepa" && quote . inputCurrency === FiatToken . EURC && rampState . to === Networks . Polygon ;
140+
141+ if ( isMoneriumPolygonOnramp ) {
142+ // For Monerium Polygon onramp, use the hash directly
143+ return {
144+ transactionExplorerLink : `https://polygonscan.com/tx/${ hash } ` ,
145+ transactionHash : hash
146+ } ;
147+ }
148+
149+ // For other cases, query AxelarScan for the execution hash and chain-specific explorer
150+ const { explorerLink, executionHash } = await getAxelarScanExecutionLink ( hash ) ;
151+
152+ return {
153+ transactionExplorerLink : explorerLink ,
154+ transactionHash : executionHash
155+ } ;
156+ } catch ( error ) {
157+ logger . error ( `Error fetching AxelarScan execution hash for ${ hash } : ${ error } ` ) ;
158+ // Fallback to original hash if fetching fails
159+ return {
160+ transactionExplorerLink : EXPLORER_LINK_BUILDERS [ hashKey ] ( hash , rampState , quote ) ,
161+ transactionHash : hash
162+ } ;
163+ }
164+ }
165+
166+ // For other hash types, use them directly
60167 return {
61168 transactionExplorerLink : EXPLORER_LINK_BUILDERS [ hashKey ] ( hash , rampState , quote ) ,
62169 transactionHash : hash
0 commit comments