mirror of https://github.com/certusone/oyster.git
only show and fetch own transactions (#107)
* fixed button with no provider * feat: fix bridge transactions * fix: connection selection * only show and fetch own transactions * Added better messages * added memo to columns Co-authored-by: bartosz-lipinski <264380+bartosz-lipinski@users.noreply.github.com>
This commit is contained in:
parent
ea2af3b06f
commit
18bda5527f
|
@ -1,4 +1,4 @@
|
||||||
import { programIds, sendTransactionWithRetry } from '@oyster/common';
|
import { programIds, sendTransactionWithRetry, sleep } from '@oyster/common';
|
||||||
import { WalletAdapter } from '@solana/wallet-base';
|
import { WalletAdapter } from '@solana/wallet-base';
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
import { WormholeFactory } from '../../contracts/WormholeFactory';
|
import { WormholeFactory } from '../../contracts/WormholeFactory';
|
||||||
|
@ -113,6 +113,17 @@ export const fromSolana = async (
|
||||||
wallet,
|
wallet,
|
||||||
[ix, fee_ix, lock_ix],
|
[ix, fee_ix, lock_ix],
|
||||||
[],
|
[],
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
() => {
|
||||||
|
setProgress({
|
||||||
|
message: 'Executing Solana Transaction',
|
||||||
|
type: 'wait',
|
||||||
|
group,
|
||||||
|
step: counter++,
|
||||||
|
});
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return steps.wait(request, transferKey, slot);
|
return steps.wait(request, transferKey, slot);
|
||||||
|
@ -124,31 +135,39 @@ export const fromSolana = async (
|
||||||
) => {
|
) => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
let completed = false;
|
let completed = false;
|
||||||
|
let unsubscribed = false;
|
||||||
let startSlot = slot;
|
let startSlot = slot;
|
||||||
|
|
||||||
let group = 'Lock assets';
|
let group = 'Lock assets';
|
||||||
const solConfirmationMessage = (current: number) =>
|
const solConfirmationMessage = (current: number) =>
|
||||||
`Awaiting ETH confirmations: ${current} out of 32`;
|
`Awaiting Solana confirmations: ${current} out of 32`;
|
||||||
|
let replaceMessage = false;
|
||||||
let slotUpdateListener = connection.onSlotChange(slot => {
|
let slotUpdateListener = connection.onSlotChange(slot => {
|
||||||
if (completed) return;
|
if (unsubscribed) {
|
||||||
const passedSlots = slot.slot - startSlot;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passedSlots = Math.min(Math.max(slot.slot - startSlot, 0), 32);
|
||||||
const isLast = passedSlots - 1 === 31;
|
const isLast = passedSlots - 1 === 31;
|
||||||
if (passedSlots < 32) {
|
if (passedSlots <= 32) {
|
||||||
setProgress({
|
setProgress({
|
||||||
message: solConfirmationMessage(passedSlots),
|
message: solConfirmationMessage(passedSlots),
|
||||||
type: isLast ? 'done' : 'wait',
|
type: isLast ? 'done' : 'wait',
|
||||||
step: counter++,
|
step: counter++,
|
||||||
group,
|
group,
|
||||||
replace: passedSlots > 0,
|
replace: replaceMessage,
|
||||||
|
});
|
||||||
|
replaceMessage = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completed || isLast) {
|
||||||
|
unsubscribed = true;
|
||||||
|
setProgress({
|
||||||
|
message: 'Awaiting guardian confirmation. (Up to few min.)',
|
||||||
|
type: 'wait',
|
||||||
|
step: counter++,
|
||||||
|
group,
|
||||||
});
|
});
|
||||||
if (isLast) {
|
|
||||||
setProgress({
|
|
||||||
message: 'Awaiting guardian confirmation',
|
|
||||||
type: 'wait',
|
|
||||||
step: counter++,
|
|
||||||
group,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -175,10 +194,19 @@ export const fromSolana = async (
|
||||||
completed = true;
|
completed = true;
|
||||||
connection.removeAccountChangeListener(accountChangeListener);
|
connection.removeAccountChangeListener(accountChangeListener);
|
||||||
connection.removeSlotChangeListener(slotUpdateListener);
|
connection.removeSlotChangeListener(slotUpdateListener);
|
||||||
|
let signatures;
|
||||||
|
|
||||||
|
while (!signatures) {
|
||||||
|
try {
|
||||||
|
signatures = await bridge.fetchSignatureStatus(
|
||||||
|
lockup.signatureAccount,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
} catch {
|
||||||
|
await sleep(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let signatures = await bridge.fetchSignatureStatus(
|
|
||||||
lockup.signatureAccount,
|
|
||||||
);
|
|
||||||
let sigData = Buffer.of(
|
let sigData = Buffer.of(
|
||||||
...signatures.reduce((previousValue, currentValue) => {
|
...signatures.reduce((previousValue, currentValue) => {
|
||||||
previousValue.push(currentValue.index);
|
previousValue.push(currentValue.index);
|
||||||
|
@ -217,7 +245,7 @@ export const fromSolana = async (
|
||||||
});
|
});
|
||||||
let tx = await wh.submitVAA(vaa);
|
let tx = await wh.submitVAA(vaa);
|
||||||
setProgress({
|
setProgress({
|
||||||
message: 'Waiting for tokens unlock to be mined...',
|
message: 'Waiting for tokens unlock to be mined... (Up to few min.)',
|
||||||
type: 'wait',
|
type: 'wait',
|
||||||
group,
|
group,
|
||||||
step: counter++,
|
step: counter++,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { BigNumber } from 'bignumber.js';
|
import { BigNumber } from 'bignumber.js';
|
||||||
import {ethers} from "ethers";
|
import { ethers } from 'ethers';
|
||||||
import { ASSET_CHAIN } from '../constants';
|
import { ASSET_CHAIN } from '../constants';
|
||||||
|
|
||||||
export interface ProgressUpdate {
|
export interface ProgressUpdate {
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {
|
||||||
import { AccountInfo } from '@solana/spl-token';
|
import { AccountInfo } from '@solana/spl-token';
|
||||||
import { TransferRequest, ProgressUpdate } from './interface';
|
import { TransferRequest, ProgressUpdate } from './interface';
|
||||||
import { WalletAdapter } from '@solana/wallet-base';
|
import { WalletAdapter } from '@solana/wallet-base';
|
||||||
import { BigNumber } from "bignumber.js";
|
import { BigNumber } from 'bignumber.js';
|
||||||
|
|
||||||
export const toSolana = async (
|
export const toSolana = async (
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
|
@ -180,7 +180,7 @@ export const toSolana = async (
|
||||||
});
|
});
|
||||||
let res = await e.approve(programIds().wormhole.bridge, amountBN);
|
let res = await e.approve(programIds().wormhole.bridge, amountBN);
|
||||||
setProgress({
|
setProgress({
|
||||||
message: 'Waiting for ETH transaction to be mined...',
|
message: 'Waiting for ETH transaction to be mined... (Up to few min.)',
|
||||||
type: 'wait',
|
type: 'wait',
|
||||||
group,
|
group,
|
||||||
step: counter++,
|
step: counter++,
|
||||||
|
@ -243,7 +243,7 @@ export const toSolana = async (
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
setProgress({
|
setProgress({
|
||||||
message: 'Waiting for ETH transaction to be mined...',
|
message: 'Waiting for ETH transaction to be mined... (Up to few min.)',
|
||||||
type: 'wait',
|
type: 'wait',
|
||||||
group,
|
group,
|
||||||
step: counter++,
|
step: counter++,
|
||||||
|
|
|
@ -7,6 +7,16 @@ import * as BufferLayout from 'buffer-layout';
|
||||||
import * as bs58 from 'bs58';
|
import * as bs58 from 'bs58';
|
||||||
import { AssetMeta } from '../bridge';
|
import { AssetMeta } from '../bridge';
|
||||||
|
|
||||||
|
export enum LockupStatus {
|
||||||
|
AWAITING_VAA,
|
||||||
|
UNCLAIMED_VAA,
|
||||||
|
COMPLETED,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LockupWithStatus extends Lockup {
|
||||||
|
status: LockupStatus;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Lockup {
|
export interface Lockup {
|
||||||
lockupAddress: PublicKey;
|
lockupAddress: PublicKey;
|
||||||
amount: BN;
|
amount: BN;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Button, Table, Tabs, notification } from 'antd';
|
import { Button, Table, Tabs, notification } from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
|
||||||
|
@ -34,11 +34,7 @@ const { TabPane } = Tabs;
|
||||||
export const RecentTransactionsTable = (props: {
|
export const RecentTransactionsTable = (props: {
|
||||||
showUserTransactions?: boolean;
|
showUserTransactions?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { loading: loadingTransfers, transfers } = useWormholeTransactions();
|
||||||
loading: loadingTransfers,
|
|
||||||
transfers,
|
|
||||||
userTransfers,
|
|
||||||
} = useWormholeTransactions();
|
|
||||||
const { provider } = useEthereum();
|
const { provider } = useEthereum();
|
||||||
const bridge = useBridge();
|
const bridge = useBridge();
|
||||||
|
|
||||||
|
@ -154,221 +150,197 @@ export const RecentTransactionsTable = (props: {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const columns = [
|
|
||||||
...baseColumns,
|
|
||||||
{
|
|
||||||
title: 'Status',
|
|
||||||
dataIndex: 'status',
|
|
||||||
key: 'status',
|
|
||||||
render(text: string, record: any) {
|
|
||||||
return {
|
|
||||||
props: { style: {} },
|
|
||||||
children: (
|
|
||||||
<span className={`${record.status?.toLowerCase()}`}>
|
|
||||||
{record.status}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const userColumns = [
|
const userColumns = useMemo(
|
||||||
...baseColumns,
|
() => [
|
||||||
{
|
...baseColumns,
|
||||||
title: 'Status',
|
{
|
||||||
dataIndex: 'status',
|
title: 'Status',
|
||||||
key: 'status',
|
dataIndex: 'status',
|
||||||
render(text: string, record: any) {
|
key: 'status',
|
||||||
const status =
|
render(text: string, record: any) {
|
||||||
completedVAAs.indexOf(record.txhash) > 0
|
const status =
|
||||||
? 'Completed'
|
completedVAAs.indexOf(record.txhash) > 0
|
||||||
: record.status;
|
? 'Completed'
|
||||||
return {
|
: record.status;
|
||||||
props: { style: {} },
|
return {
|
||||||
children: (
|
props: { style: {} },
|
||||||
<>
|
children: (
|
||||||
<span className={`${record.status?.toLowerCase()}`}>
|
<>
|
||||||
{status}
|
<span className={`${record.status?.toLowerCase()}`}>
|
||||||
</span>
|
{status}
|
||||||
{status === 'Failed' ? (
|
</span>
|
||||||
<Button
|
{status === 'Failed' ? (
|
||||||
onClick={() => {
|
<Button
|
||||||
const NotificationContent = () => {
|
onClick={() => {
|
||||||
const [activeSteps, setActiveSteps] = useState<
|
const NotificationContent = () => {
|
||||||
ProgressUpdate[]
|
const [activeSteps, setActiveSteps] = useState<
|
||||||
>([]);
|
ProgressUpdate[]
|
||||||
let counter = 0;
|
>([]);
|
||||||
useEffect(() => {
|
let counter = 0;
|
||||||
(async () => {
|
useEffect(() => {
|
||||||
const signer = provider?.getSigner();
|
(async () => {
|
||||||
if (!signer || !bridge) {
|
const signer = provider?.getSigner();
|
||||||
setActiveSteps([
|
if (!signer || !bridge) {
|
||||||
...activeSteps,
|
setActiveSteps([
|
||||||
{
|
...activeSteps,
|
||||||
message: 'Connect your Ethereum Wallet',
|
{
|
||||||
type: 'error',
|
message: 'Connect your Ethereum Wallet',
|
||||||
group: 'error',
|
type: 'error',
|
||||||
step: counter++,
|
group: 'error',
|
||||||
},
|
step: counter++,
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
const lockup = record.lockup;
|
|
||||||
let vaa = lockup.vaa;
|
|
||||||
for (let i = vaa.length; i > 0; i--) {
|
|
||||||
if (vaa[i] == 0xff) {
|
|
||||||
vaa = vaa.slice(0, i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let signatures = await bridge.fetchSignatureStatus(
|
|
||||||
lockup.signatureAccount,
|
|
||||||
);
|
|
||||||
let sigData = Buffer.of(
|
|
||||||
...signatures.reduce(
|
|
||||||
(previousValue, currentValue) => {
|
|
||||||
previousValue.push(currentValue.index);
|
|
||||||
previousValue.push(...currentValue.signature);
|
|
||||||
|
|
||||||
return previousValue;
|
|
||||||
},
|
},
|
||||||
new Array<number>(),
|
]);
|
||||||
),
|
} else {
|
||||||
);
|
const lockup = record.lockup;
|
||||||
|
let vaa = lockup.vaa;
|
||||||
|
for (let i = vaa.length; i > 0; i--) {
|
||||||
|
if (vaa[i] == 0xff) {
|
||||||
|
vaa = vaa.slice(0, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let signatures = await bridge.fetchSignatureStatus(
|
||||||
|
lockup.signatureAccount,
|
||||||
|
);
|
||||||
|
let sigData = Buffer.of(
|
||||||
|
...signatures.reduce(
|
||||||
|
(previousValue, currentValue) => {
|
||||||
|
previousValue.push(currentValue.index);
|
||||||
|
previousValue.push(
|
||||||
|
...currentValue.signature,
|
||||||
|
);
|
||||||
|
|
||||||
vaa = Buffer.concat([
|
return previousValue;
|
||||||
vaa.slice(0, 5),
|
},
|
||||||
Buffer.of(signatures.length),
|
new Array<number>(),
|
||||||
sigData,
|
),
|
||||||
vaa.slice(6),
|
);
|
||||||
]);
|
|
||||||
let wh = WormholeFactory.connect(
|
|
||||||
programIds().wormhole.bridge,
|
|
||||||
signer,
|
|
||||||
);
|
|
||||||
let group = 'Finalizing transfer';
|
|
||||||
setActiveSteps([
|
|
||||||
...activeSteps,
|
|
||||||
{
|
|
||||||
message: 'Sign the claim...',
|
|
||||||
type: 'wait',
|
|
||||||
group,
|
|
||||||
step: counter++,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
let tx = await wh.submitVAA(vaa);
|
|
||||||
setActiveSteps([
|
|
||||||
...activeSteps,
|
|
||||||
{
|
|
||||||
message:
|
|
||||||
'Waiting for tokens unlock to be mined...',
|
|
||||||
type: 'wait',
|
|
||||||
group,
|
|
||||||
step: counter++,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
await tx.wait(1);
|
|
||||||
setActiveSteps([
|
|
||||||
...activeSteps,
|
|
||||||
{
|
|
||||||
message: 'Execution of VAA succeeded',
|
|
||||||
type: 'done',
|
|
||||||
group,
|
|
||||||
step: counter++,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [setActiveSteps]);
|
|
||||||
|
|
||||||
return (
|
vaa = Buffer.concat([
|
||||||
<div>
|
vaa.slice(0, 5),
|
||||||
<div
|
Buffer.of(signatures.length),
|
||||||
style={{
|
sigData,
|
||||||
textAlign: 'left',
|
vaa.slice(6),
|
||||||
display: 'flex',
|
]);
|
||||||
flexDirection: 'column',
|
let wh = WormholeFactory.connect(
|
||||||
}}
|
programIds().wormhole.bridge,
|
||||||
>
|
signer,
|
||||||
{(() => {
|
);
|
||||||
let group = '';
|
let group = 'Finalizing transfer';
|
||||||
return activeSteps.map((step, i) => {
|
setActiveSteps([
|
||||||
let prevGroup = group;
|
...activeSteps,
|
||||||
group = step.group;
|
{
|
||||||
let newGroup = prevGroup !== group;
|
message: 'Sign the claim...',
|
||||||
return (
|
type: 'wait',
|
||||||
<>
|
group,
|
||||||
{newGroup && <span>{group}</span>}
|
step: counter++,
|
||||||
<span style={{ marginLeft: 15 }}>
|
},
|
||||||
{typeToIcon(
|
]);
|
||||||
step.type,
|
let tx = await wh.submitVAA(vaa);
|
||||||
activeSteps.length - 1 === i,
|
setActiveSteps([
|
||||||
)}{' '}
|
...activeSteps,
|
||||||
{step.message}
|
{
|
||||||
</span>
|
message:
|
||||||
</>
|
'Waiting for tokens unlock to be mined... (Up to few min.)',
|
||||||
);
|
type: 'wait',
|
||||||
});
|
group,
|
||||||
})()}
|
step: counter++,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await tx.wait(1);
|
||||||
|
setActiveSteps([
|
||||||
|
...activeSteps,
|
||||||
|
{
|
||||||
|
message: 'Execution of VAA succeeded',
|
||||||
|
type: 'done',
|
||||||
|
group,
|
||||||
|
step: counter++,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setCompletedVAAs([
|
||||||
|
...completedVAAs,
|
||||||
|
record.txhash,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [setActiveSteps]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textAlign: 'left',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(() => {
|
||||||
|
let group = '';
|
||||||
|
return activeSteps.map((step, i) => {
|
||||||
|
let prevGroup = group;
|
||||||
|
group = step.group;
|
||||||
|
let newGroup = prevGroup !== group;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{newGroup && <span>{group}</span>}
|
||||||
|
<span style={{ marginLeft: 15 }}>
|
||||||
|
{typeToIcon(
|
||||||
|
step.type,
|
||||||
|
activeSteps.length - 1 === i,
|
||||||
|
)}{' '}
|
||||||
|
{step.message}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
notification.open({
|
notification.open({
|
||||||
message: '',
|
message: '',
|
||||||
duration: 0,
|
duration: 0,
|
||||||
placement: 'bottomLeft',
|
placement: 'bottomLeft',
|
||||||
description: <NotificationContent />,
|
description: <NotificationContent />,
|
||||||
className: 'custom-class',
|
className: 'custom-class',
|
||||||
style: {
|
style: {
|
||||||
width: 500,
|
width: 500,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
shape="circle"
|
shape="circle"
|
||||||
size="large"
|
size="large"
|
||||||
type="text"
|
type="text"
|
||||||
style={{ color: '#547595', fontSize: '18px' }}
|
style={{ color: '#547595', fontSize: '18px' }}
|
||||||
title={'Retry Transaction'}
|
title={'Retry Transaction'}
|
||||||
icon={<SyncOutlined />}
|
icon={<SyncOutlined />}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
];
|
[completedVAAs, bridge, provider],
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div id={'recent-tx-container'}>
|
<div id={'recent-tx-container'}>
|
||||||
<div className={'home-subtitle'} style={{ marginBottom: '70px' }}>
|
<div className={'home-subtitle'} style={{ marginBottom: '70px' }}>
|
||||||
Transactions
|
My Recent Transactions
|
||||||
</div>
|
</div>
|
||||||
<Tabs defaultActiveKey="1" centered>
|
<Table
|
||||||
<TabPane tab="Recent Transactions" key="1">
|
scroll={{
|
||||||
<Table
|
scrollToFirstRowOnChange: false,
|
||||||
scroll={{
|
x: 900,
|
||||||
scrollToFirstRowOnChange: false,
|
}}
|
||||||
x: 900,
|
dataSource={transfers.sort((a, b) => b.date - a.date)}
|
||||||
}}
|
columns={userColumns}
|
||||||
dataSource={transfers.sort((a, b) => b.date - a.date)}
|
loading={loadingTransfers}
|
||||||
columns={columns}
|
/>
|
||||||
loading={loadingTransfers}
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="My Transactions" key="2">
|
|
||||||
<Table
|
|
||||||
scroll={{
|
|
||||||
scrollToFirstRowOnChange: false,
|
|
||||||
x: 900,
|
|
||||||
}}
|
|
||||||
dataSource={userTransfers.sort((a, b) => b.date - a.date)}
|
|
||||||
columns={userColumns}
|
|
||||||
loading={loadingTransfers}
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const useCorrectNetwork = () => {
|
||||||
if (chainId === 5) {
|
if (chainId === 5) {
|
||||||
setHasCorrespondingNetworks(env === 'testnet');
|
setHasCorrespondingNetworks(env === 'testnet');
|
||||||
} else if (chainId === 1) {
|
} else if (chainId === 1) {
|
||||||
setHasCorrespondingNetworks(env === 'mainnet-beta');
|
setHasCorrespondingNetworks(env.includes('mainnet-beta'));
|
||||||
} else {
|
} else {
|
||||||
setHasCorrespondingNetworks(false);
|
setHasCorrespondingNetworks(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
|
notify,
|
||||||
|
programIds,
|
||||||
useConnection,
|
useConnection,
|
||||||
useConnectionConfig,
|
useConnectionConfig,
|
||||||
programIds,
|
|
||||||
notify,
|
|
||||||
useWallet,
|
useWallet,
|
||||||
ParsedAccountBase,
|
|
||||||
} from '@oyster/common';
|
} from '@oyster/common';
|
||||||
import {
|
import {
|
||||||
WORMHOLE_PROGRAM_ID,
|
|
||||||
POSTVAA_INSTRUCTION,
|
POSTVAA_INSTRUCTION,
|
||||||
TRANSFER_ASSETS_OUT_INSTRUCTION,
|
TRANSFER_ASSETS_OUT_INSTRUCTION,
|
||||||
|
WORMHOLE_PROGRAM_ID,
|
||||||
} from '../utils/ids';
|
} from '../utils/ids';
|
||||||
import { ASSET_CHAIN } from '../utils/assets';
|
import { ASSET_CHAIN } from '../utils/assets';
|
||||||
import { useEthereum } from '../contexts';
|
import { useEthereum } from '../contexts';
|
||||||
import {
|
import {
|
||||||
|
AccountInfo,
|
||||||
Connection,
|
Connection,
|
||||||
|
ParsedAccountData,
|
||||||
PartiallyDecodedInstruction,
|
PartiallyDecodedInstruction,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
|
RpcResponseAndContext,
|
||||||
} from '@solana/web3.js';
|
} from '@solana/web3.js';
|
||||||
import {
|
import {
|
||||||
bridgeAuthorityKey,
|
bridgeAuthorityKey,
|
||||||
|
LockupStatus,
|
||||||
|
LockupWithStatus,
|
||||||
|
SolanaBridge,
|
||||||
TransferOutProposalLayout,
|
TransferOutProposalLayout,
|
||||||
|
WormholeFactory,
|
||||||
} from '@solana/bridge-sdk';
|
} from '@solana/bridge-sdk';
|
||||||
|
|
||||||
import bs58 from 'bs58';
|
import bs58 from 'bs58';
|
||||||
|
@ -31,11 +37,10 @@ import {
|
||||||
useCoingecko,
|
useCoingecko,
|
||||||
} from '../contexts/coingecko';
|
} from '../contexts/coingecko';
|
||||||
import { BigNumber } from 'bignumber.js';
|
import { BigNumber } from 'bignumber.js';
|
||||||
import { WormholeFactory } from '@solana/bridge-sdk';
|
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
import { useBridge } from '../contexts/bridge';
|
import { useBridge } from '../contexts/bridge';
|
||||||
import { SolanaBridge } from '@solana/bridge-sdk';
|
|
||||||
import BN from 'bn.js';
|
import BN from 'bn.js';
|
||||||
|
import { keccak256 } from 'ethers/utils';
|
||||||
|
|
||||||
type WrappedTransferMeta = {
|
type WrappedTransferMeta = {
|
||||||
chain: number;
|
chain: number;
|
||||||
|
@ -59,6 +64,86 @@ type WrappedTransferMeta = {
|
||||||
|
|
||||||
const transferCache = new Map<string, WrappedTransferMeta>();
|
const transferCache = new Map<string, WrappedTransferMeta>();
|
||||||
|
|
||||||
|
const queryOwnWrappedMetaTransactions = async (
|
||||||
|
authorityKey: PublicKey,
|
||||||
|
connection: Connection,
|
||||||
|
setTransfers: (arr: WrappedTransferMeta[]) => void,
|
||||||
|
provider: ethers.providers.Web3Provider,
|
||||||
|
bridge?: SolanaBridge,
|
||||||
|
owner?: PublicKey | null,
|
||||||
|
) => {
|
||||||
|
if (owner && bridge) {
|
||||||
|
const transfers = new Map<string, WrappedTransferMeta>();
|
||||||
|
let wh = WormholeFactory.connect(programIds().wormhole.bridge, provider);
|
||||||
|
const res: RpcResponseAndContext<
|
||||||
|
Array<{ pubkey: PublicKey; account: AccountInfo<ParsedAccountData> }>
|
||||||
|
> = await connection.getParsedTokenAccountsByOwner(
|
||||||
|
owner,
|
||||||
|
{ programId: programIds().token },
|
||||||
|
'single',
|
||||||
|
);
|
||||||
|
let lockups: LockupWithStatus[] = [];
|
||||||
|
for (const acc of res.value) {
|
||||||
|
const accLockups = await bridge.fetchTransferProposals(acc.pubkey);
|
||||||
|
lockups.push(
|
||||||
|
...accLockups.map(v => {
|
||||||
|
return {
|
||||||
|
status: LockupStatus.AWAITING_VAA,
|
||||||
|
...v,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
for (let lockup of lockups) {
|
||||||
|
if (lockup.vaaTime === undefined || lockup.vaaTime === 0) continue;
|
||||||
|
|
||||||
|
let signingData = lockup.vaa.slice(lockup.vaa[5] * 66 + 6);
|
||||||
|
for (let i = signingData.length; i > 0; i--) {
|
||||||
|
if (signingData[i] == 0xff) {
|
||||||
|
signingData = signingData.slice(0, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let hash = keccak256(signingData);
|
||||||
|
let submissionStatus = await wh.consumedVAAs(hash);
|
||||||
|
|
||||||
|
lockup.status = submissionStatus
|
||||||
|
? LockupStatus.COMPLETED
|
||||||
|
: LockupStatus.UNCLAIMED_VAA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const ls of lockups) {
|
||||||
|
const txhash = ls.lockupAddress.toBase58();
|
||||||
|
let assetAddress: string = '';
|
||||||
|
if (ls.assetChain !== ASSET_CHAIN.Solana) {
|
||||||
|
assetAddress = Buffer.from(ls.assetAddress.slice(12)).toString('hex');
|
||||||
|
} else {
|
||||||
|
assetAddress = new PublicKey(ls.assetAddress).toBase58();
|
||||||
|
}
|
||||||
|
const dec = new BigNumber(10).pow(new BigNumber(ls.assetDecimals));
|
||||||
|
const rawAmount = new BigNumber(ls.amount.toString());
|
||||||
|
const amount = rawAmount.div(dec).toNumber();
|
||||||
|
transfers.set(txhash, {
|
||||||
|
publicKey: ls.lockupAddress,
|
||||||
|
amount,
|
||||||
|
date: ls.vaaTime,
|
||||||
|
chain: ls.assetChain,
|
||||||
|
address: assetAddress,
|
||||||
|
decimals: 9,
|
||||||
|
txhash,
|
||||||
|
explorer: `https://explorer.solana.com/address/${txhash}`,
|
||||||
|
lockup: ls,
|
||||||
|
status:
|
||||||
|
ls.status === LockupStatus.UNCLAIMED_VAA
|
||||||
|
? 'Failed'
|
||||||
|
: ls.status === LockupStatus.AWAITING_VAA
|
||||||
|
? 'In Process'
|
||||||
|
: 'Completed',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTransfers([...transfers.values()]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const queryWrappedMetaTransactions = async (
|
const queryWrappedMetaTransactions = async (
|
||||||
authorityKey: PublicKey,
|
authorityKey: PublicKey,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
|
@ -238,12 +323,11 @@ export const useWormholeTransactions = () => {
|
||||||
const { tokenMap: ethTokens } = useEthereum();
|
const { tokenMap: ethTokens } = useEthereum();
|
||||||
const { tokenMap } = useConnectionConfig();
|
const { tokenMap } = useConnectionConfig();
|
||||||
const { coinList } = useCoingecko();
|
const { coinList } = useCoingecko();
|
||||||
const { wallet, connected: walletConnected } = useWallet();
|
const { wallet } = useWallet();
|
||||||
const bridge = useBridge();
|
const bridge = useBridge();
|
||||||
|
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [transfers, setTransfers] = useState<WrappedTransferMeta[]>([]);
|
const [transfers, setTransfers] = useState<WrappedTransferMeta[]>([]);
|
||||||
const [userTransfers, setUserTransfers] = useState<WrappedTransferMeta[]>([]);
|
|
||||||
const [amountInUSD, setAmountInUSD] = useState<number>(0);
|
const [amountInUSD, setAmountInUSD] = useState<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -263,26 +347,17 @@ export const useWormholeTransactions = () => {
|
||||||
(window as any).ethereum,
|
(window as any).ethereum,
|
||||||
);
|
);
|
||||||
// query wrapped assets that were imported to solana from other chains
|
// query wrapped assets that were imported to solana from other chains
|
||||||
queryWrappedMetaTransactions(
|
queryOwnWrappedMetaTransactions(
|
||||||
authorityKey,
|
authorityKey,
|
||||||
connection,
|
connection,
|
||||||
setTransfers,
|
setTransfers,
|
||||||
provider,
|
provider,
|
||||||
bridge,
|
bridge,
|
||||||
|
wallet?.publicKey,
|
||||||
).then(() => setLoading(false));
|
).then(() => setLoading(false));
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [connection, setTransfers]);
|
}, [connection, setTransfers, wallet?.publicKey]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (transfers && walletConnected && wallet?.publicKey) {
|
|
||||||
setUserTransfers(
|
|
||||||
transfers.filter(t => {
|
|
||||||
return t.owner === wallet?.publicKey?.toBase58();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [wallet, walletConnected, transfers]);
|
|
||||||
|
|
||||||
const coingeckoTimer = useRef<number>(0);
|
const coingeckoTimer = useRef<number>(0);
|
||||||
const dataSourcePriceQuery = useCallback(async () => {
|
const dataSourcePriceQuery = useCallback(async () => {
|
||||||
|
@ -350,7 +425,6 @@ export const useWormholeTransactions = () => {
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
transfers,
|
transfers,
|
||||||
userTransfers,
|
|
||||||
totalInUSD: amountInUSD,
|
totalInUSD: amountInUSD,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
} from '@solana/spl-token-registry';
|
} from '@solana/spl-token-registry';
|
||||||
|
|
||||||
export type ENV =
|
export type ENV =
|
||||||
|
| 'mainnet-beta (Serum)'
|
||||||
| 'mainnet-beta'
|
| 'mainnet-beta'
|
||||||
| 'testnet'
|
| 'testnet'
|
||||||
| 'devnet'
|
| 'devnet'
|
||||||
|
@ -31,10 +32,15 @@ export type ENV =
|
||||||
|
|
||||||
export const ENDPOINTS = [
|
export const ENDPOINTS = [
|
||||||
{
|
{
|
||||||
name: 'mainnet-beta' as ENV,
|
name: 'mainnet-beta (Serum)' as ENV,
|
||||||
endpoint: 'https://solana-api.projectserum.com/',
|
endpoint: 'https://solana-api.projectserum.com/',
|
||||||
ChainId: ChainId.MainnetBeta,
|
ChainId: ChainId.MainnetBeta,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'mainnet-beta' as ENV,
|
||||||
|
endpoint: 'https://api.mainnet-beta.solana.com',
|
||||||
|
ChainId: ChainId.MainnetBeta,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'testnet' as ENV,
|
name: 'testnet' as ENV,
|
||||||
endpoint: clusterApiUrl('testnet'),
|
endpoint: clusterApiUrl('testnet'),
|
||||||
|
@ -412,6 +418,7 @@ export const sendTransactionWithRetry = async (
|
||||||
commitment: Commitment = 'singleGossip',
|
commitment: Commitment = 'singleGossip',
|
||||||
includesFeePayer: boolean = false,
|
includesFeePayer: boolean = false,
|
||||||
block?: BlockhashAndFeeCalculator,
|
block?: BlockhashAndFeeCalculator,
|
||||||
|
beforeSend?: () => void,
|
||||||
) => {
|
) => {
|
||||||
let transaction = new Transaction();
|
let transaction = new Transaction();
|
||||||
instructions.forEach(instruction => transaction.add(instruction));
|
instructions.forEach(instruction => transaction.add(instruction));
|
||||||
|
@ -436,6 +443,10 @@ export const sendTransactionWithRetry = async (
|
||||||
transaction = await wallet.signTransaction(transaction);
|
transaction = await wallet.signTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (beforeSend) {
|
||||||
|
beforeSend();
|
||||||
|
}
|
||||||
|
|
||||||
const { txid, slot } = await sendSignedTransaction({
|
const { txid, slot } = await sendSignedTransaction({
|
||||||
connection,
|
connection,
|
||||||
signedTransaction: transaction,
|
signedTransaction: transaction,
|
||||||
|
@ -521,7 +532,7 @@ export async function sendSignedTransaction({
|
||||||
}
|
}
|
||||||
throw new Error(JSON.stringify(simulateResult.err));
|
throw new Error(JSON.stringify(simulateResult.err));
|
||||||
}
|
}
|
||||||
throw new Error('Transaction failed');
|
// throw new Error('Transaction failed');
|
||||||
} finally {
|
} finally {
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@ export const PROGRAM_IDS = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const setProgramIds = (envName: string) => {
|
export const setProgramIds = (envName: string) => {
|
||||||
let instance = PROGRAM_IDS.find(env => env.name === envName);
|
let instance = PROGRAM_IDS.find(env => envName.indexOf(env.name) >= 0);
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue