From 112495564a4de0eab0c79be34f28b95047769e9d Mon Sep 17 00:00:00 2001 From: odiinnn <38395645+odiinnn@users.noreply.github.com> Date: Tue, 7 Oct 2025 12:43:56 +0300 Subject: [PATCH 1/5] Add PactSwap aggregator and fee tracking functionality (#1) - Implemented PactSwap volume fetching in bridge-aggregators/pactswap/index.ts. - Created fee tracking helpers in fees/pactswap/helpers.ts. - Added fee fetching logic in fees/pactswap/index.ts. - Introduced support for COINWEB chain in helpers/chains.ts. - Established PactSwap supported chains and volume fetching logic in helpers/aggregators/pactswap.ts. --- bridge-aggregators/pactswap/index.ts | 23 +++++ fees/pactswap/helpers.ts | 144 +++++++++++++++++++++++++++ fees/pactswap/index.ts | 49 +++++++++ helpers/aggregators/pactswap.ts | 56 +++++++++++ helpers/chains.ts | 1 + 5 files changed, 273 insertions(+) create mode 100644 bridge-aggregators/pactswap/index.ts create mode 100644 fees/pactswap/helpers.ts create mode 100644 fees/pactswap/index.ts create mode 100644 helpers/aggregators/pactswap.ts diff --git a/bridge-aggregators/pactswap/index.ts b/bridge-aggregators/pactswap/index.ts new file mode 100644 index 0000000000..b5e2fb7535 --- /dev/null +++ b/bridge-aggregators/pactswap/index.ts @@ -0,0 +1,23 @@ +import { FetchOptions, FetchV2, SimpleAdapter } from "../../adapters/types"; +import { fetchVolumeFromPactswapAPI, PACTSWAP_SUPPORTED_CHAINS } from "../../helpers/aggregators/pactswap"; + +const fetch: FetchV2 = async (options: FetchOptions) => { + const timeframes = await fetchVolumeFromPactswapAPI(options.chain, options.startTimestamp, options.endTimestamp); + + const dailyVolume = options.createBalances(); + timeframes.forEach(({ volume }) => { + dailyVolume.addUSDValue(Number(volume)); + }); + + return { + dailyBridgeVolume: dailyVolume, + }; +}; + +const adapter: SimpleAdapter = { + version: 2, + fetch, + chains: Array.from(PACTSWAP_SUPPORTED_CHAINS) +}; + +export default adapter; \ No newline at end of file diff --git a/fees/pactswap/helpers.ts b/fees/pactswap/helpers.ts new file mode 100644 index 0000000000..eb347be5e3 --- /dev/null +++ b/fees/pactswap/helpers.ts @@ -0,0 +1,144 @@ +import { gql, request } from "graphql-request" + +const COINWEB_URL = 'https://api-cloud.coinweb.io' +const queryCount = gql` + query( + $start: NaiveDateTime + $end: NaiveDateTime + $feeSmartContract: String + ) { + network(net: BNB) { + countClaims( + issuer: { + FromSmartContract: $feeSmartContract + } + firstPartOfKey: ["FUNDS"] + datetimeRange: { from: $start, to: $end } + ) + } +} +` + +type QueryFee = { + network: { + fetchClaims: { + claims: Array<{ + blockInfo: FeeClaimBlockInfo + claim: FeeClaimClaim + }> + hasNextPage: boolean + nextPageAnchor: { + hash: string + height: number + } + } + } +} + +type FeeClaimBlockInfo = { + height: number + time: string + hash: string +} +type FeeClaimClaim = { + content: { + body: string + feesStored: `0x${string}` + key: string + } +} +const queryFee = gql` +query ( + $feeSmartContract: String! + $start: NaiveDateTime + $end: NaiveDateTime + $countToReturn: Int! +) { + network(net: BNB) { + fetchClaims( + issuer: { FromSmartContract: $feeSmartContract } + firstPartOfKey: ["FUNDS"] + maxClaimsToReturn: $countToReturn + datetimeRange: { from: $start, to: $end } + ) { + claims { + blockInfo { + height + time + hash + } + claim { + content { + body + feesStored + key + } + issuer + } + } + hasNextPage + nextPageAnchor { + hash + height + } + } + } +}` + +export const getFeeSmartContract = async(): Promise => { + const data = await fetch('https://app.pactswap.io/build-info.json') + const json = await data.json() + try { + const feeSmartContract = json.BTC.L2_CONTRACT_ADDRESS_MAKER.module.instance.parameters.content.owner; + return feeSmartContract + } catch (error) { + throw new Error('Failed to get fee smart contract', { cause: error }) + } +} + +export const getFeeClaimsCount = async(feeSmartContract: string, startTime: string, endTime: string): Promise => { + const countResponse = await request(COINWEB_URL, queryCount, { + start: startTime, + end: endTime, + feeSmartContract: feeSmartContract, + }); + + const count = countResponse.network.countClaims; + return Number(count); +} + +const getFeeClaims = async(feeSmartContract: string, startTime: string, endTime: string, countToReturn: number) => { + const feeResponse = await request(COINWEB_URL, queryFee, { + start: startTime, + end: endTime, + feeSmartContract: feeSmartContract, + countToReturn: countToReturn, + }); + return feeResponse.network.fetchClaims; +} + +export const oneDayInSeconds = 86400; + +export const getFee = async(feeSmartContract: string, startTime: string, endTime: string, countToReturn: number): Promise => { + const feeClaims = await getFeeClaims(feeSmartContract, startTime, endTime, countToReturn); + if (feeClaims.claims.length === 1) { + return Number(feeClaims.claims[0].claim.content.feesStored); + } + + let nearesToEndTime: typeof feeClaims.claims[0] | undefined = undefined; + + for (const claim of feeClaims.claims) { + if (new Date(claim.blockInfo.time) <= new Date(startTime)) { + nearesToEndTime = claim; + } + } + + const bottomClaim = nearesToEndTime || feeClaims.claims[0]; + const bottomFee = Number(bottomClaim.claim.content.feesStored); + + const latesFeeClaim = feeClaims.claims.at(-1); + const latestFee = Number(latesFeeClaim?.claim.content.feesStored); + + const fee = latestFee - bottomFee; + return fee; +} \ No newline at end of file diff --git a/fees/pactswap/index.ts b/fees/pactswap/index.ts new file mode 100644 index 0000000000..512c6564be --- /dev/null +++ b/fees/pactswap/index.ts @@ -0,0 +1,49 @@ +import { FetchOptions, FetchV2, SimpleAdapter } from "../../adapters/types"; +import { CHAIN } from "../../helpers/chains"; +import { getFee, getFeeClaimsCount, getFeeSmartContract, oneDayInSeconds } from "./helpers"; + +const fetch: FetchV2 = async (options: FetchOptions) => { + const dailyFees = options.createBalances(); + const feeSmartContract = await getFeeSmartContract(); + const formattedFeeSmartContract = feeSmartContract.startsWith('0x') ? feeSmartContract : `0x${feeSmartContract}`; + + let startTime = new Date(options.fromTimestamp * 1000).toISOString().replace('Z', ''); + const endTime = new Date(options.toTimestamp * 1000).toISOString().replace('Z', ''); + + let count = await getFeeClaimsCount(formattedFeeSmartContract, startTime, endTime); + + if (!count) { + return { dailyFees } + } + + const maxDays = 30; + let currentDays = 0; + + while (count === 1 && currentDays < maxDays) { + startTime = new Date(new Date(startTime).getTime() - oneDayInSeconds * 1000 * (currentDays + 1)).toISOString().replace('Z', ''); + count = await getFeeClaimsCount(formattedFeeSmartContract, startTime, endTime); + currentDays++; + } + + const fee = await getFee(formattedFeeSmartContract, startTime, endTime, count); + + dailyFees.addGasToken(BigInt(fee).toString()); + + return { dailyFees } +}; + +const adapter: SimpleAdapter = { + version: 2, + adapter: { + [CHAIN.COINWEB]: { + fetch, + } + }, + methodology: { + Fees: 'All fees paid by users for finalising transactions.', + Revenue: 'All fees are distributed to PactSwap fee pool.', + ProtocolRevenue: 'All fees are distributed to PactSwap fee pool.', + } +}; + +export default adapter; diff --git a/helpers/aggregators/pactswap.ts b/helpers/aggregators/pactswap.ts new file mode 100644 index 0000000000..3d2e1d6913 --- /dev/null +++ b/helpers/aggregators/pactswap.ts @@ -0,0 +1,56 @@ +import fetchURL from "../../utils/fetchURL"; +import { CHAIN } from "../chains"; + +export const PACTSWAP_SUPPORTED_CHAINS = [ + CHAIN.BSC, + CHAIN.BITCOIN, + CHAIN.DOGE, + CHAIN.ETHEREUM, + CHAIN.LITECOIN, + CHAIN.POLYGON, + CHAIN.TRON, +] as const; + +const INDEXER_URL = "https://pactswap-indexer.coinhq.store/api/v1/metrics/"; + +// CAIP-122 chain IDs to bigint for Bitcoin like chains +// # Bitcoin mainnet (see https://github.com/bitcoin/bips/blob/master/bip-0122.mediawiki#definition-of-chain-id) +// bip122:000000000019d6689c085ae165831e93 +// # Litecoin mainnet +// bip122:12a765e31ffd4059bada1e25190f6e98 +// # Dogecoin mainnet +const PACTSWAP_CHAIN_ID_MAP: Record = { + [CHAIN.BSC]: 56n, + [CHAIN.BITCOIN]: BigInt("0x".concat("000000000019d6689c085ae165831e93")), + [CHAIN.DOGE]: BigInt("0x".concat("1a91e3dace36e2be3bf030a65679fe82")), + [CHAIN.ETHEREUM]: 1n, + [CHAIN.LITECOIN]: BigInt("0x".concat("12a765e31ffd4059bada1e25190f6e98")), + [CHAIN.POLYGON]: 137n, + [CHAIN.TRON]: 128n, +}; + +export const fetchVolumeFromPactswapAPI = async ( + chain: string, + startTimestamp: number, + endTimestamp: number +): Promise> => { + const chainId = PACTSWAP_CHAIN_ID_MAP[chain]; + try { + const response = await fetchURL( + `${INDEXER_URL}swaps/timeseries/volume?chain_id=${chainId}×tamp_gt=${startTimestamp}×tamp_lt=${endTimestamp}&interval=day` + ) as Array<{ + timestamp: string; + volume: string; + }>; + return response + .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) + .slice(0, 2) + .filter(Boolean); + } catch (error) { + console.error(error); + return []; + } +}; \ No newline at end of file diff --git a/helpers/chains.ts b/helpers/chains.ts index bcca12d3d7..b655452482 100644 --- a/helpers/chains.ts +++ b/helpers/chains.ts @@ -1,5 +1,6 @@ // Use export enum CHAIN { + COINWEB = "coinweb", PACASWAP = "pacaswap", PEAQ = "peaq", CHROMIA = "chromia", From 148ba7e7dd0a8553d681350631558d3159eb1cae Mon Sep 17 00:00:00 2001 From: odiinnn <38395645+odiinnn@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:08:29 +0300 Subject: [PATCH 2/5] Add PactSwap adapter implementation (#1) - Introduced a new file for PactSwap volume fetching in dexs/pactswap/index.ts. - Implemented the fetch function to aggregate daily volume from the PactSwap API. - Defined the adapter structure with supported chains for integration. --- dexs/pactswap/index.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 dexs/pactswap/index.ts diff --git a/dexs/pactswap/index.ts b/dexs/pactswap/index.ts new file mode 100644 index 0000000000..d9fd459be6 --- /dev/null +++ b/dexs/pactswap/index.ts @@ -0,0 +1,23 @@ +import { FetchOptions, FetchV2, SimpleAdapter } from "../../adapters/types"; +import { fetchVolumeFromPactswapAPI, PACTSWAP_SUPPORTED_CHAINS } from "../../helpers/aggregators/pactswap"; + +const fetch: FetchV2 = async (options: FetchOptions) => { + const timeframes = await fetchVolumeFromPactswapAPI(options.chain, options.startTimestamp, options.endTimestamp); + + const dailyVolume = options.createBalances(); + timeframes.forEach(({ volume }) => { + dailyVolume.addUSDValue(Number(volume)); + }); + + return { + dailyVolume, + }; +}; + +const adapter: SimpleAdapter = { + version: 2, + fetch, + chains: Array.from(PACTSWAP_SUPPORTED_CHAINS) +}; + +export default adapter; \ No newline at end of file From b475556010a6c5edf57cc9274e8485c39c281b06 Mon Sep 17 00:00:00 2001 From: odiinnn <38395645+odiinnn@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:09:55 +0300 Subject: [PATCH 3/5] Restore COINWEB chain in helpers/chains.ts --- helpers/chains.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/chains.ts b/helpers/chains.ts index b655452482..cfadb11465 100644 --- a/helpers/chains.ts +++ b/helpers/chains.ts @@ -1,6 +1,5 @@ // Use export enum CHAIN { - COINWEB = "coinweb", PACASWAP = "pacaswap", PEAQ = "peaq", CHROMIA = "chromia", @@ -273,4 +272,5 @@ export enum CHAIN { HIBACHI = "hibachi", SATORI = "satori", SHIBARIUM = "shibarium", + COINWEB = "coinweb", } From 8b961a9ba3f50290cd6718cc43db1a7a1228a5d4 Mon Sep 17 00:00:00 2001 From: odiinnn <38395645+odiinnn@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:11:43 +0300 Subject: [PATCH 4/5] Remove PactSwap adapter implementation from bridge-aggregators/pactswap/index.ts --- bridge-aggregators/pactswap/index.ts | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 bridge-aggregators/pactswap/index.ts diff --git a/bridge-aggregators/pactswap/index.ts b/bridge-aggregators/pactswap/index.ts deleted file mode 100644 index b5e2fb7535..0000000000 --- a/bridge-aggregators/pactswap/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { FetchOptions, FetchV2, SimpleAdapter } from "../../adapters/types"; -import { fetchVolumeFromPactswapAPI, PACTSWAP_SUPPORTED_CHAINS } from "../../helpers/aggregators/pactswap"; - -const fetch: FetchV2 = async (options: FetchOptions) => { - const timeframes = await fetchVolumeFromPactswapAPI(options.chain, options.startTimestamp, options.endTimestamp); - - const dailyVolume = options.createBalances(); - timeframes.forEach(({ volume }) => { - dailyVolume.addUSDValue(Number(volume)); - }); - - return { - dailyBridgeVolume: dailyVolume, - }; -}; - -const adapter: SimpleAdapter = { - version: 2, - fetch, - chains: Array.from(PACTSWAP_SUPPORTED_CHAINS) -}; - -export default adapter; \ No newline at end of file From 276c7291063f03c7b4ee1fe321a9dbcd1c1ab096 Mon Sep 17 00:00:00 2001 From: odiinnn <38395645+odiinnn@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:26:42 +0300 Subject: [PATCH 5/5] Refactor PactSwap integration by moving API functions to a new pactswap.ts file --- dexs/pactswap/index.ts | 2 +- {helpers/aggregators => dexs/pactswap}/pactswap.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {helpers/aggregators => dexs/pactswap}/pactswap.ts (97%) diff --git a/dexs/pactswap/index.ts b/dexs/pactswap/index.ts index d9fd459be6..34abec09ad 100644 --- a/dexs/pactswap/index.ts +++ b/dexs/pactswap/index.ts @@ -1,5 +1,5 @@ import { FetchOptions, FetchV2, SimpleAdapter } from "../../adapters/types"; -import { fetchVolumeFromPactswapAPI, PACTSWAP_SUPPORTED_CHAINS } from "../../helpers/aggregators/pactswap"; +import { fetchVolumeFromPactswapAPI, PACTSWAP_SUPPORTED_CHAINS } from "./pactswap"; const fetch: FetchV2 = async (options: FetchOptions) => { const timeframes = await fetchVolumeFromPactswapAPI(options.chain, options.startTimestamp, options.endTimestamp); diff --git a/helpers/aggregators/pactswap.ts b/dexs/pactswap/pactswap.ts similarity index 97% rename from helpers/aggregators/pactswap.ts rename to dexs/pactswap/pactswap.ts index 3d2e1d6913..af1979fb7d 100644 --- a/helpers/aggregators/pactswap.ts +++ b/dexs/pactswap/pactswap.ts @@ -1,5 +1,5 @@ import fetchURL from "../../utils/fetchURL"; -import { CHAIN } from "../chains"; +import { CHAIN } from "../../helpers/chains"; export const PACTSWAP_SUPPORTED_CHAINS = [ CHAIN.BSC,