Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ yalc.lock
/dist/
.vercel
.continueignore
.agents/
e2e.log
docs/specs/**
codex-resume
skills-lock.json
codex-resume
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@ import { VStack } from "native-base";

import { MPBBridge } from "./MPBBridge";
import { useMPBBridgeFeatureController } from "./feature/useMPBBridgeFeatureController";
import type { MPBBridgeReadOnlyUrls } from "./types";

interface IMPBBridgeControllerProps {
withHistory?: boolean;
onBridgeStart?: () => void;
onBridgeSuccess?: () => void;
onBridgeFailed?: (e: Error) => void;
bridgeReadOnlyUrls?: MPBBridgeReadOnlyUrls;
}

export const MPBBridgeController: React.FC<IMPBBridgeControllerProps> = ({
onBridgeStart,
onBridgeSuccess,
onBridgeFailed
onBridgeFailed,
bridgeReadOnlyUrls
}) => {
const bridgeProps = useMPBBridgeFeatureController({
onBridgeStart,
onBridgeSuccess,
onBridgeFailed
onBridgeFailed,
bridgeReadOnlyUrls
});

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@ import { useEthers } from "@usedapp/core";
import { ethers } from "ethers";
import { useMPBBridgeFlow, useG$Decimals, SupportedChains, VALIDATION_REASONS } from "@gooddollar/web3sdk-v2";

import { BridgeProvider, MPBBridgeProps } from "../types";
import { BridgeProvider, MPBBridgeProps, MPBBridgeReadOnlyUrls } from "../types";
import { getDefaultTargetChain } from "../utils/chainHelpers";

interface UseMPBBridgeFeatureControllerParams {
onBridgeStart?: () => void;
onBridgeSuccess?: () => void;
onBridgeFailed?: (e: Error) => void;
bridgeReadOnlyUrls?: MPBBridgeReadOnlyUrls;
}

const ZERO_FEE = { nativeFee: ethers.BigNumber.from(0), zroFee: ethers.BigNumber.from(0) };

export const useMPBBridgeFeatureController = ({
onBridgeStart,
onBridgeSuccess,
onBridgeFailed
onBridgeFailed,
bridgeReadOnlyUrls
}: UseMPBBridgeFeatureControllerParams): MPBBridgeProps => {
const { chainId, account } = useEthers();
const [bridgeProvider, setBridgeProvider] = useState<BridgeProvider>("layerzero");
Expand Down Expand Up @@ -176,6 +178,7 @@ export const useMPBBridgeFeatureController = ({
onBridgeStart: onBridgeStartHandler,
onBridgeFailed,
onBridgeSuccess,
bridgeReadOnlyUrls,
bridgeProvider,
onBridgeProviderChange: setBridgeProvider
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import { useMPBBridgeUiState } from "./useMPBBridgeUiState";

const DEBOUNCE_MS = 300;
const TRANSACTION_HISTORY_DEBOUNCE_MS = 2000;
const CHAIN_NAME_TO_ID: Record<string, SupportedChains> = {
fuse: SupportedChains.FUSE,
celo: SupportedChains.CELO,
mainnet: SupportedChains.MAINNET,
xdc: SupportedChains.XDC
};

const FLOW_PENDING_STATES = new Set([
"awaiting_network_switch",
Expand Down Expand Up @@ -134,6 +140,7 @@ export const useMPBBridgeViewController = ({
onBridgeStart,
onBridgeFailed,
onBridgeSuccess,
bridgeReadOnlyUrls,
bridgeProvider: propBridgeProvider,
onBridgeProviderChange
}: MPBBridgeProps): MPBBridgeViewModel => {
Expand Down Expand Up @@ -165,8 +172,12 @@ export const useMPBBridgeViewController = ({
closeAllDropdowns
} = useMPBBridgeUiState();

const bridgeHistoryChainIds = useMemo(
Comment thread
L03TJ3 marked this conversation as resolved.
Outdated
() => Array.from(new Set([CHAIN_NAME_TO_ID[sourceChain], CHAIN_NAME_TO_ID[targetChain]].filter(Boolean))),
[sourceChain, targetChain]
);
const { realTransactionHistory, historyLoading, historyRefreshing, historyErrorsByChain, refreshHistory } =
useDebouncedTransactionHistory(TRANSACTION_HISTORY_DEBOUNCE_MS);
useDebouncedTransactionHistory(TRANSACTION_HISTORY_DEBOUNCE_MS, bridgeReadOnlyUrls, bridgeHistoryChainIds);
const { getBalanceForChain } = useChainBalances();

const gdValue = getBalanceForChain(sourceChain);
Expand Down
13 changes: 10 additions & 3 deletions packages/good-design/src/apps/bridge/mpbridge/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CurrencyValue } from "@usedapp/core";
import { useG$Amounts, useProductionG$Balance, G$Amount, useGetEnvChainId } from "@gooddollar/web3sdk-v2";
import { BigNumber } from "ethers";
import { fetchBridgeFees, useMPBBridgeHistory } from "@gooddollar/web3sdk-v2";
import type { IMPBFees, IMPBLimits } from "./types";
import type { IMPBFees, IMPBLimits, MPBBridgeHistoryChainIds, MPBBridgeReadOnlyUrls } from "./types";
import { convertTransaction } from "./utils";

const CACHE_KEY = "mpb-bridge-fees-cache";
Expand Down Expand Up @@ -175,14 +175,21 @@ export const useChainBalances = () => {
return { getBalanceForChain };
};

export const useDebouncedTransactionHistory = (delay = 1000) => {
export const useDebouncedTransactionHistory = (
delay = 1000,
bridgeReadOnlyUrls?: MPBBridgeReadOnlyUrls,
bridgeHistoryChainIds?: MPBBridgeHistoryChainIds
) => {
const {
historySorted: realTransactionHistory,
initialLoading,
refreshing,
errorsByChain,
refreshHistory
} = useMPBBridgeHistory() ?? {};
} = useMPBBridgeHistory({
readOnlyUrls: bridgeReadOnlyUrls,
chainIds: bridgeHistoryChainIds
}) ?? {};
const [debouncedHistory, setDebouncedHistory] = useState(realTransactionHistory);
const timeoutRef = useRef<NodeJS.Timeout>();

Expand Down
10 changes: 9 additions & 1 deletion packages/good-design/src/apps/bridge/mpbridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ export { MPBBridge } from "./MPBBridge";
export { MPBBridgeController } from "./MPBBridgeController";
export { useMPBBridgeFeatureController } from "./feature/useMPBBridgeFeatureController";
export { BridgeTransactionCard, BridgeTransactionList } from "./MPBBridgeTransactionCard";
export type { MPBBridgeProps, IMPBLimits, IMPBFees, BridgeProvider, BridgeTransaction } from "./types";
export type {
MPBBridgeProps,
MPBBridgeHistoryChainIds,
MPBBridgeReadOnlyUrls,
IMPBLimits,
IMPBFees,
BridgeProvider,
BridgeTransaction
} from "./types";

export { ChainSelector } from "./ChainSelector";
export { BridgeProviderSelector } from "./BridgeProviderSelector";
Expand Down
5 changes: 5 additions & 0 deletions packages/good-design/src/apps/bridge/mpbridge/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { BigNumber } from "ethers";
import type { SupportedChains } from "@gooddollar/web3sdk-v2";

export type BridgeProvider = "axelar" | "layerzero";

export type MPBBridgeReadOnlyUrls = Partial<Record<number, string>>;
export type MPBBridgeHistoryChainIds = SupportedChains[];

export type BridgeTransaction = {
id: string;
transactionHash: string;
Expand Down Expand Up @@ -62,6 +66,7 @@ export interface MPBBridgeProps {
onBridgeStart?: (sourceChain: string, targetChain: string) => Promise<void>;
onBridgeFailed?: (error: Error) => void;
onBridgeSuccess?: () => void;
bridgeReadOnlyUrls?: MPBBridgeReadOnlyUrls;
bridgeProvider?: BridgeProvider;
onBridgeProviderChange?: (provider: BridgeProvider) => void;
}
4 changes: 2 additions & 2 deletions packages/sdk-v2/src/hooks/useMulticallAtChain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef } from "react";

import { Result } from "@ethersproject/abi";
import { BigNumber } from "@ethersproject/bignumber";
import { BaseProvider, JsonRpcProvider, Provider } from "@ethersproject/providers";
import { BaseProvider, JsonRpcProvider, Provider, StaticJsonRpcProvider } from "@ethersproject/providers";
import { Contract } from "ethers";
import { noop } from "lodash";

Expand Down Expand Up @@ -86,7 +86,7 @@ export const useReadOnlyProvider = (chainId: number) => {
return (factory as any)() as JsonRpcProvider;
}

const provider = new JsonRpcProvider(factory as any);
const provider = new StaticJsonRpcProvider(factory as any, chainId);
Comment thread
L03TJ3 marked this conversation as resolved.

provider.pollingInterval = pollingInterval;
return provider;
Expand Down
1 change: 1 addition & 0 deletions packages/sdk-v2/src/sdk/mpbridge/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from "./useBridgeValidators";
export * from "./useGetMPBBridgeData";
export * from "./useLayerZeroFee";
export * from "./useMPBBridge";
export * from "./useMPBBridgeHistory.helpers";
export * from "./useMPBBridgeHistory";
export * from "./useMPBG$TokenContract";
export * from "./useProductionG$Balance";
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/* eslint-env jest */

import {
createAccountEventTopics,
createBlockChunks,
dedupeLogs,
getAddressTopic,
getErrorsByChain,
mergeBridgeHistoryCache,
MPBBridgeHistoryCache
Expand All @@ -16,6 +19,35 @@ describe("useMPBBridgeHistory helpers", () => {
]);
});

it("creates indexed account topics for bridge history log filters", () => {
const account = "0xc1bA0ACD3030321851889309497663998D87D8d6";
const accountTopic = "0x000000000000000000000000c1ba0acd3030321851889309497663998d87d8d6";

expect(getAddressTopic(account)).toBe(accountTopic);
expect(createAccountEventTopics("0xtopic", account)).toEqual([
["0xtopic", accountTopic],
["0xtopic", null, accountTopic]
]);
});

it("falls back to the event topic when the account address is unavailable", () => {
expect(createAccountEventTopics("0xtopic")).toEqual([["0xtopic"]]);
expect(createAccountEventTopics("0xtopic", "invalid")).toEqual([["0xtopic"]]);
});

it("dedupes logs that match both indexed account filters", () => {
expect(
dedupeLogs([
{ transactionHash: "0x1", logIndex: 2, value: "from-match" },
{ transactionHash: "0x1", logIndex: 2, value: "to-match" },
{ transactionHash: "0x2", logIndex: 1, value: "other" }
])
).toEqual([
{ transactionHash: "0x1", logIndex: 2, value: "to-match" },
{ transactionHash: "0x2", logIndex: 1, value: "other" }
]);
});

it("merges history rows, prunes old cache entries, and keeps chain sync state", () => {
const nowMs = new Date("2026-06-29T00:00:00.000Z").getTime();
const recentTimestamp = Math.floor(nowMs / 1000) - 60;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,41 @@ export const createBlockChunks = (fromBlock: number, toBlock: number, chunkSize
return chunks;
};

export const getAddressTopic = (address?: string) => {
if (!address) {
return undefined;
}

const normalizedAddress = address.toLowerCase();

if (!/^0x[0-9a-f]{40}$/.test(normalizedAddress)) {
return undefined;
}

return `0x${normalizedAddress.slice(2).padStart(64, "0")}`;
};

export const createAccountEventTopics = (eventTopic: string, account?: string) => {
const accountTopic = getAddressTopic(account);

if (!accountTopic) {
return [[eventTopic]];
}

return [
[eventTopic, accountTopic],
[eventTopic, null, accountTopic]
];
};

export const dedupeLogs = <T extends { transactionHash: string; logIndex?: number }>(logs: T[]) => {
const logsByKey = new Map<string, T>();

logs.forEach(log => logsByKey.set(`${log.transactionHash}:${log.logIndex ?? 0}`, log));

return Array.from(logsByKey.values());
};

export const pruneExpiredEvents = (events: CachedBridgeEvent[], minTimestamp: number) =>
events.filter(event => Number(event.timestamp || 0) >= minTimestamp);

Expand Down
Loading