Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added RouteContext and useTrackTransfer hook #7

Merged
merged 2 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 4 additions & 1 deletion wormhole-connect/src/AppRouter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from 'react';
import React, { useContext, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';

Expand All @@ -24,6 +24,7 @@ import { useExternalSearch } from 'hooks/useExternalSearch';
import internalConfig from 'config';

import BridgeV2 from 'views/v2/Bridge';
import { RouteContext } from 'contexts/RouteContext';

const useStyles = makeStyles()((theme: any) => ({
appContent: {
Expand All @@ -49,6 +50,7 @@ interface Props {
function AppRouter(props: Props) {
const { classes } = useStyles();
const dispatch = useDispatch();
const routeContext = useContext(RouteContext);

// We update the global config once when WormholeConnect is first mounted, if a custom
// config was provided.
Expand Down Expand Up @@ -80,6 +82,7 @@ function AppRouter(props: Props) {
if (prevRoute === redeemRoute && route !== redeemRoute) {
dispatch(clearRedeem());
dispatch(clearWallets());
routeContext.clear();
internalConfig.wh.registerProviders(); // reset providers that may have been set during transfer
}
// reset transfer state on leave
Expand Down
5 changes: 4 additions & 1 deletion wormhole-connect/src/WormholeConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getDesignTokens, dark } from './theme';
import ErrorBoundary from './components/ErrorBoundary';
import { WormholeConnectConfig } from './config/types';
import { WormholeConnectPartialTheme } from 'theme';
import { RouteProvider } from './contexts/RouteContext';

export interface WormholeConnectProps {
// theme can be updated at any time to change the colors of Connect
Expand All @@ -32,7 +33,9 @@ export default function WormholeConnect({
<ThemeProvider theme={muiTheme}>
<ScopedCssBaseline enableColorScheme>
<ErrorBoundary>
<AppRouter config={config} />
<RouteProvider>
<AppRouter config={config} />
</RouteProvider>
</ErrorBoundary>
</ScopedCssBaseline>
</ThemeProvider>
Expand Down
44 changes: 44 additions & 0 deletions wormhole-connect/src/contexts/RouteContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { useCallback } from 'react';
import { Network, routes } from '@wormhole-foundation/sdk';

interface RouteContextType {
route: routes.Route<Network> | null;
receipt: routes.Receipt | null;
setRoute: (route: routes.Route<Network>) => void;
setReceipt: (receipt: routes.Receipt) => void;
clear: () => void;
}

export const RouteContext = React.createContext<RouteContextType>({
route: null,
receipt: null,
setRoute: () => {
// Keep the empty function for initial context value
},
setReceipt: () => {
// Keep the empty function for initial context value
},
clear: () => {
// Keep the empty function for initial context value
},
});

export const RouteProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [route, setRoute] = React.useState<routes.Route<Network> | null>(null);
const [receipt, setReceipt] = React.useState<routes.Receipt | null>(null);

const clear = useCallback(() => {
setRoute(null);
setReceipt(null);
}, []);

return (
<RouteContext.Provider
value={{ route, receipt, setRoute, setReceipt, clear }}
>
{children}
</RouteContext.Provider>
);
};
57 changes: 57 additions & 0 deletions wormhole-connect/src/hooks/useTrackTransfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { isCompleted } from '@wormhole-foundation/sdk';
import { RouteContext } from 'contexts/RouteContext';
import { useContext, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { setTransferComplete } from 'store/redeem';
import { sleep } from 'utils';

const TRACK_TIMEOUT = 120 * 1000;

// TODO: document this hook, especially since it sets and depends on the receipt state
const useTrackTransfer = () => {
const dispatch = useDispatch();

const routeContext = useContext(RouteContext);

useEffect(() => {
let isActive = true;

const track = async () => {
const { route, receipt } = routeContext;
if (!route || !receipt) {
return;
}
while (isActive && !isCompleted(receipt)) {
try {
// TODO: the timeout may be longer for chains with slower finality times
// but we will retry so maybe it doesn't matter
const result = await route.track(receipt, TRACK_TIMEOUT).next();
if (result.done || !isActive) {
break;
}
const currentReceipt = result.value;
if (currentReceipt.state !== receipt.state) {
routeContext.setReceipt(currentReceipt);
if (isCompleted(currentReceipt)) {
dispatch(setTransferComplete(true));
}
break;
}
} catch (e) {
console.error('Error tracking transfer:', e);
}
// retry
// TODO: exponential backoff depending on the current state?
await sleep(5000);
}
};

track();

return () => {
isActive = false;
};
}, [routeContext]);
};

export default useTrackTransfer;
4 changes: 2 additions & 2 deletions wormhole-connect/src/routes/operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from './types';
import { TokenPrices } from 'store/tokenPrices';

import { routes } from '@wormhole-foundation/sdk';
import { Network, routes } from '@wormhole-foundation/sdk';

import { getRoute } from './mappings';

Expand Down Expand Up @@ -436,7 +436,7 @@ export class Operator {
recipientAddress: string,
destToken: string,
routeOptions: any,
): Promise<string | routes.Receipt> {
): Promise<[routes.Route<Network>, routes.Receipt]> {
const r = this.getRoute(route);
return await r.send(
token,
Expand Down
35 changes: 24 additions & 11 deletions wormhole-connect/src/routes/sdkv2/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@
NATIVE_GAS_DROPOFF_SUPPORTED = false;
AUTOMATIC_DEPOSIT = false;

constructor(
readonly rc: routes.RouteConstructor,
routeType: Route,
) {
constructor(readonly rc: routes.RouteConstructor, routeType: Route) {
super();
this.TYPE = routeType;
}
Expand Down Expand Up @@ -324,7 +321,7 @@
const srcChain = (await this.getV2ChainContext(fromChainV1)).context;
const dstChain = (await this.getV2ChainContext(toChainV1)).context;

const [_route, quote] = await this.getQuote(

Check warning on line 324 in wormhole-connect/src/routes/sdkv2/route.ts

View workflow job for this annotation

GitHub Actions / lint

'_route' is assigned a value but never used
amountIn.toString(),
srcTokenV2,
dstTokenV2,
Expand Down Expand Up @@ -401,7 +398,10 @@
destToken: string,
options: any,
): Promise<
SourceInitiatedTransferReceipt | SourceFinalizedTransferReceipt<any>
[
routes.Route<Network>,
SourceInitiatedTransferReceipt | SourceFinalizedTransferReceipt<any>,
]
> {
const fromChainV2 = await this.getV2ChainContext(fromChainV1);
const toChainV2 = await this.getV2ChainContext(toChainV1);
Expand Down Expand Up @@ -460,14 +460,14 @@
receipt.state == TransferState.SourceInitiated ||
receipt.state == TransferState.SourceFinalized
) {
return receipt;
return [route, receipt];
}
}

throw new Error('Never got a SourceInitiate state in receipt');
}

public redeem(
public async redeem(
destChain: ChainName | ChainId,
messageInfo: SignedMessage,
recipient: string,
Expand Down Expand Up @@ -496,16 +496,29 @@
];
}

public getTransferSourceInfo<T extends TransferInfoBaseParams>(
async getTransferSourceInfo<T extends TransferInfoBaseParams>(
params: T,
): Promise<TransferDisplayData> {
throw new Error('Method not implemented.');
return [
{
title: 'test',
value: 'testvalue',
},
];
}

public getTransferDestInfo<T extends TransferDestInfoBaseParams>(
async getTransferDestInfo<T extends TransferDestInfoBaseParams>(
params: T,
): Promise<TransferDestInfo> {
throw new Error('Method not implemented.');
return {
route: this.TYPE,
displayData: [
{
title: 'test',
value: 'testvalue',
},
],
};
}

async getRelayerFee(
Expand Down
43 changes: 35 additions & 8 deletions wormhole-connect/src/routes/sdkv2/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
TxHash,
} from '@wormhole-foundation/sdk';
//import { EvmUnsignedTransaction } from '@wormhole-foundation/sdk-evm';
import { ChainId, ChainName, SendResult } from 'sdklegacy';
import { ChainId, ChainName } from 'sdklegacy';
import config, { getWormholeContextV2 } from 'config';
import { signAndSendTransaction, TransferWallet } from 'utils/wallet';
import * as ethers5 from 'ethers5';
Expand All @@ -17,6 +17,8 @@
import { TransactionRequest } from '@ethersproject/abstract-provider';
import { Deferrable } from '@ethersproject/properties';

import { SignRequest } from 'utils/wallet/types';

// Utility class that bridges between legacy Connect signer interface and SDKv2 signer interface
export class SDKv2Signer<N extends Network, C extends Chain>
implements SignAndSendSigner<N, C>
Expand Down Expand Up @@ -54,14 +56,14 @@
}

async signAndSend(txs: UnsignedTransaction<N, C>[]): Promise<TxHash[]> {
let txHashes: TxHash[] = [];

Check failure on line 59 in wormhole-connect/src/routes/sdkv2/signer.ts

View workflow job for this annotation

GitHub Actions / lint

'txHashes' is never reassigned. Use 'const' instead

for (let tx of txs) {

Check failure on line 61 in wormhole-connect/src/routes/sdkv2/signer.ts

View workflow job for this annotation

GitHub Actions / lint

'tx' is never reassigned. Use 'const' instead
let sendResult: SendResult = this.toSendResult(tx);
let request: SignRequest = this.createSignRequest(tx);

Check failure on line 62 in wormhole-connect/src/routes/sdkv2/signer.ts

View workflow job for this annotation

GitHub Actions / lint

'request' is never reassigned. Use 'const' instead

let txId = await signAndSendTransaction(

Check failure on line 64 in wormhole-connect/src/routes/sdkv2/signer.ts

View workflow job for this annotation

GitHub Actions / lint

'txId' is never reassigned. Use 'const' instead
this._chainNameV1,
sendResult,
request,
TransferWallet.SENDING,
this._options,
);
Expand All @@ -71,31 +73,56 @@
return txHashes;
}

private toSendResult(tx: UnsignedTransaction<N, C>): SendResult {
// This takes an SDKv2 UnsignedTransaction and prepares it for use by xlabs-wallet-adapter
private createSignRequest(tx: UnsignedTransaction<N, C>): SignRequest {
const platform = chainToPlatform(tx.chain);

switch (platform) {
case 'Evm':
// TODO switch multi-provider to ethers 6
// and remove this ethers5-to-6 conversion
let serialized = ethers6.Transaction.from({

Check failure on line 84 in wormhole-connect/src/routes/sdkv2/signer.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected lexical declaration in case block

Check failure on line 84 in wormhole-connect/src/routes/sdkv2/signer.ts

View workflow job for this annotation

GitHub Actions / lint

'serialized' is never reassigned. Use 'const' instead
to: tx.transaction.to,
data: tx.transaction.data,
}).unsignedSerialized;
let tx5: ethers5.Transaction =

Check failure on line 88 in wormhole-connect/src/routes/sdkv2/signer.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected lexical declaration in case block

Check failure on line 88 in wormhole-connect/src/routes/sdkv2/signer.ts

View workflow job for this annotation

GitHub Actions / lint

'tx5' is never reassigned. Use 'const' instead
ethers5.utils.parseTransaction(serialized);
let unsignedTx: Deferrable<TransactionRequest> = {

Check failure on line 90 in wormhole-connect/src/routes/sdkv2/signer.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected lexical declaration in case block
to: tx5.to,
type: tx5.type as number,
chainId: tx5.chainId,
data: tx5.data,
};
return unsignedTx as SendResult;
return {
platform,
transaction: unsignedTx,
};
case 'Solana':
return tx.transaction.transaction;
return {
platform,
transaction: tx.transaction.transaction,
};
case 'Cosmwasm':
debugger;
return {
platform,
transaction: tx,
};
case 'Sui':
return {
platform,
transaction: tx,
};
case 'Aptos':
return {
platform,
transaction: tx.transaction,
};
default:
console.warn(`toSendResult is unimplemented for platform ${platform}`);
return tx as SendResult;
throw new Error(
`toSendResult is unimplemented for platform ${platform}`,
);
//return tx as SendResult;
}
}

Expand Down
10 changes: 3 additions & 7 deletions wormhole-connect/src/utils/wallet/aptos.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import {
//AptosContext,
SendResult,
//WormholeContext,
} from 'sdklegacy';
import { Wallet } from '@xlabs-libs/wallet-aggregator-core';
import {
AptosSnapAdapter,
Expand All @@ -21,6 +16,7 @@ import { AptosWallet } from '@xlabs-libs/wallet-aggregator-aptos';
import { Types } from 'aptos';

import config from 'config';
import { SignRequestAptos } from './types';

const aptosWallets = {
aptos: new AptosWallet(new AptosWalletAdapter()),
Expand All @@ -45,11 +41,11 @@ export function fetchOptions() {
}

export async function signAndSendTransaction(
transaction: SendResult,
request: SignRequestAptos,
wallet: Wallet | undefined,
) {
// The wallets do not handle Uint8Array serialization
const payload = transaction as Types.EntryFunctionPayload;
const payload = request.transaction as Types.EntryFunctionPayload;
if (payload.arguments) {
payload.arguments = payload.arguments.map((a: any) =>
a instanceof Uint8Array ? Array.from(a) : a,
Expand Down
Loading
Loading