Skip to content

Commit

Permalink
[update] Koni_story - swap feature #4
Browse files Browse the repository at this point in the history
  • Loading branch information
Thiendekaco committed Feb 14, 2025
1 parent 17626b7 commit 8ebe427
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 61 deletions.
149 changes: 99 additions & 50 deletions packages/extension-koni-ui/src/Popup/AiAgent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { WalletConnectContext } from '@subwallet/extension-koni-ui/contexts/Wall
import { useNotification, useSelector, useTranslation } from '@subwallet/extension-koni-ui/hooks';
import { useLocalStorage } from '@subwallet/extension-koni-ui/hooks/common/useLocalStorage';
import { makeTransfer, subscribeTransactionById, wcSignMessageRequest } from '@subwallet/extension-koni-ui/messaging';
import { handleSwapRequest, handleSwapStep } from '@subwallet/extension-koni-ui/messaging/transaction/swap';
import { getLatestSwapQuote, handleSwapRequest, handleSwapStep, validateSwapProcess } from '@subwallet/extension-koni-ui/messaging/transaction/swap';
import { RootState } from '@subwallet/extension-koni-ui/stores';
import { ThemeProps } from '@subwallet/extension-koni-ui/types';
import { AiTransactionData, noop, SwapAiRequest, transformAiMessageData, validateSignature } from '@subwallet/extension-koni-ui/utils';
Expand Down Expand Up @@ -67,6 +67,7 @@ const Component = (props: Props): React.ReactElement => {
const [account, setAccount] = useState<BookaAccount | undefined>(apiSDK.account);

const chainInfoMap = useSelector((state: RootState) => state.chainStore.chainInfoMap);
const assetRegistry = useSelector((state: RootState) => state.assetRegistry.assetRegistry);
const { activeModal } = useContext(ModalContext);
const { goBack } = useDefaultNavigate();
const [messages, setMessages] = useState<MessageType[]>([]);
Expand Down Expand Up @@ -95,6 +96,7 @@ const Component = (props: Props): React.ReactElement => {
const { wcAccount } = useSelector((state) => state.accountState);

const submitTxRef = useRef(false);
const assetRegistryMapCurrent = useRef(assetRegistry);

const chatMessagesAreaRef = useRef<ChatMessagesAreaRef>(null);
const chatMessagesAreaRefCurrent = chatMessagesAreaRef.current;
Expand Down Expand Up @@ -695,67 +697,111 @@ const Component = (props: Props): React.ReactElement => {

const swapRequestResult = await handleSwapRequest(request);

if (swapRequestResult.quote.optimalQuote) {
submitFunc = handleSwapStep({
process: swapRequestResult.process,
currentStep: swapRequestResult.process.steps.length - 1,
quote: swapRequestResult.quote.optimalQuote,
address: wcAccount.address,
slippage: request.slippage
});
}
const submitData = async (step: number): Promise<SWTransactionResponse> => {
const isFirstStep = step === 0;
const isLastStep = step === swapRequestResult.process.steps.length - 1;
const currentOptimalSwapPath = swapRequestResult.process;
const currentQuote = swapRequestResult.quote.optimalQuote;

if (currentQuote) {
if (isFirstStep) {
const validatePromise = validateSwapProcess({
address: wcAccount.address,
process: currentOptimalSwapPath,
selectedQuote: currentQuote
});

const _errors = await validatePromise;

if (_errors.length) {
throw _errors[0];
} else {
return await submitData(step + 1);
}
} else {
let latestOptimalQuote = currentQuote;

if (swapRequestResult.process.steps.length > 2 && isLastStep) {
if (swapRequestResult.quote.optimalQuote) {
const latestSwapQuote = await getLatestSwapQuote(request);

if (latestSwapQuote.optimalQuote) {
latestOptimalQuote = latestSwapQuote.optimalQuote;
}
}
}

const responseSwapStep = await handleSwapStep({
process: currentOptimalSwapPath,
currentStep: step,
quote: latestOptimalQuote,
address: wcAccount.address,
slippage: request.slippage
});

if (isLastStep) {
return responseSwapStep;
} else {
return await submitData(step + 1);
}
}
} else {
throw new Error('No optimal quote');
}
};

submitFunc = submitData(0);
}

const isTestnet = (aiTransactionInfo.data as SwapAiRequest).isTestnet;

if (submitFunc) {
submitFunc
.then((rs) => {
if (rs.errors.length) {
console.log('Tx error', rs.errors);

// Handle error
if (rs.errors[0].message.toLowerCase().includes('rejected by user')) {
addPendingMessage({ message: 'Hmm, seems like you cancelled the transaction. Let me know if you want to resume it!', type: 'apiMessage' });
}
submitFunc.then((rs) => {
if (rs.errors.length) {
console.log('Tx error', rs.errors);

return;
// Handle error
if (rs.errors[0].message.toLowerCase().includes('rejected by user')) {
addPendingMessage({ message: 'Hmm, seems like you cancelled the transaction. Let me know if you want to resume it!', type: 'apiMessage' });
}

if (rs.id) {
const handleResult = (data: SWTransactionBrief) => {
let messageToResponse: string | undefined;
return;
}

if (data.status === ExtrinsicStatus.SUBMITTING) {
// Handle on submit
messageToResponse = 'Your transaction has been submitted! Let’s give it a moment for the network to process...';
} else if (data.status === ExtrinsicStatus.SUCCESS) {
// Handle on success
const explorerUrl = getExplorerUrl(data.extrinsicHash, isTestnet);
if (rs.id) {
const handleResult = (data: SWTransactionBrief) => {
let messageToResponse: string | undefined;

messageToResponse = `All done! Your transaction is completed, and here’s the link for you to view on the explorer: <a href='${explorerUrl}' target='_blank'>${explorerUrl}</a>`;
} else if (data.status === ExtrinsicStatus.FAIL) {
const explorerUrl = getExplorerUrl(data.extrinsicHash, isTestnet);
if (data.status === ExtrinsicStatus.SUBMITTING) {
// Handle on submit
messageToResponse = 'Your transaction has been submitted! Let’s give it a moment for the network to process...';
} else if (data.status === ExtrinsicStatus.SUCCESS) {
// Handle on success
const explorerUrl = getExplorerUrl(data.extrinsicHash, isTestnet);

messageToResponse = `Oops, the transaction has failed. You can view it on the explorer: <a href='${explorerUrl}' target='_blank'>${explorerUrl}</a>. Would you like to try again?`;
} else if (data.status === ExtrinsicStatus.UNKNOWN) {
messageToResponse = 'Hmmm, there seems to be some unknown errors that get in the way. I’d suggest you come back at a later time and try again!';
} else if (data.status === ExtrinsicStatus.TIMEOUT) {
const explorerUrl = getExplorerUrl(data.extrinsicHash, isTestnet);
messageToResponse = `All done! Your transaction is completed, and here’s the link for you to view on the explorer: <a href='${explorerUrl}' target='_blank'>${explorerUrl}</a>`;
} else if (data.status === ExtrinsicStatus.FAIL) {
const explorerUrl = getExplorerUrl(data.extrinsicHash, isTestnet);

messageToResponse = `Uh oh, the transaction has timed out. This is due to the transaction taking much longer than expected. You can check your address on the explorer to see if the transaction is completed or not: <a href='${explorerUrl}' target='_blank'>${explorerUrl}</a>`;
}
messageToResponse = `Oops, the transaction has failed. You can view it on the explorer: <a href='${explorerUrl}' target='_blank'>${explorerUrl}</a>. Would you like to try again?`;
} else if (data.status === ExtrinsicStatus.UNKNOWN) {
messageToResponse = 'Hmmm, there seems to be some unknown errors that get in the way. I’d suggest you come back at a later time and try again!';
} else if (data.status === ExtrinsicStatus.TIMEOUT) {
const explorerUrl = getExplorerUrl(data.extrinsicHash, isTestnet);

if (messageToResponse) {
addPendingMessage({ message: messageToResponse, type: 'apiMessage' });
}
};
messageToResponse = `Uh oh, the transaction has timed out. This is due to the transaction taking much longer than expected. You can check your address on the explorer to see if the transaction is completed or not: <a href='${explorerUrl}' target='_blank'>${explorerUrl}</a>`;
}

subscribeTransactionById({ id: rs.id }, handleResult)
.then(handleResult)
.catch(console.error);
}
})
if (messageToResponse) {
addPendingMessage({ message: messageToResponse, type: 'apiMessage' });
}
};

subscribeTransactionById({ id: rs.id }, handleResult)
.then(handleResult)
.catch(console.error);
}
})
.catch((err: Error) => {
// Handle error
addPendingMessage({ message: `Oops, the transaction has failed. Would you like to try again?<pre>${err.message}</pre>`, type: 'apiMessage' });
Expand Down Expand Up @@ -815,6 +861,10 @@ const Component = (props: Props): React.ReactElement => {
}
}, [onSubmitSwapTx, onSubmitTransferTx]);

useEffect(() => {
assetRegistryMapCurrent.current = assetRegistry;
}, [assetRegistry]);

useEffect(() => {
const chatflowData = getLocalStorageChatflow(props.chatflowid);
const chatMessage = (() => {
Expand Down Expand Up @@ -1015,9 +1065,8 @@ const Component = (props: Props): React.ReactElement => {
const lastMessage = messages[messages.length - 1];

if (lastMessage.type === 'apiMessage') {
const converted = transformAiMessageData(lastMessage.message);
const converted = transformAiMessageData(lastMessage.message, assetRegistryMapCurrent.current);

console.log('converted', converted);
setAiTransactionInfo({ ...converted, aiMessageId: lastMessage.messageId });
} else {
setAiTransactionInfo(undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,21 +219,26 @@ const SwapTransactionConfirmation = styled(Component)<Props>(({ theme: { token }
fontWeight: token.fontWeightStrong,
lineHeight: token.lineHeight
},
'.__recipient-item .__account-name': {
'.__recipient-item .__account-item-name': {
fontSize: 14,
color: token.colorWhite,
color: `${token.colorTextDark2} !important`,
fontWeight: token.bodyFontWeight,
lineHeight: token.lineHeight
},
'.__quote-rate-confirm .__value': {

'.__recipient-item .__account-item-address': {
color: `${token.colorTextDark2} !important`
},

'& .__quote-rate-confirm .__value': {
fontSize: 14,
color: token.colorWhite,
color: `${token.colorTextDark2} !important`,
fontWeight: token.bodyFontWeight,
lineHeight: token.lineHeight
},
'.__estimate-transaction-fee .__value': {
fontSize: 14,
color: token.colorWhite,
color: `${token.colorTextDark2} !important`,
fontWeight: token.bodyFontWeight,
lineHeight: token.lineHeight
},
Expand All @@ -260,6 +265,10 @@ const SwapTransactionConfirmation = styled(Component)<Props>(({ theme: { token }
color: token.colorTextLabel,
fontWeight: token.bodyFontWeight,
lineHeight: token.lineHeight
},

'.alert-title': {
color: `${token.colorText} !important`
}
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ const SwapRoute = styled(Component)<Props>(({ theme: { token } }: Props) => {
fontWeight: token.bodyFontWeight,
lineHeight: token.lineHeightSM,
color: token.colorTextTertiary,
position: 'absolute',
'white-space': 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
Expand Down
14 changes: 9 additions & 5 deletions packages/extension-koni-ui/src/utils/ai/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { _parseAssetRefKey } from '@subwallet/extension-base/services/chain-service/utils';
import { _getAssetDecimals, _parseAssetRefKey } from '@subwallet/extension-base/services/chain-service/utils';
import { RequestTransfer, SwapRequest } from '@subwallet/extension-base/types';
import { IpAssetParams } from '@subwallet/extension-koni-ui/connector/booka/types';
import { AssetRegistryStore } from '@subwallet/extension-koni-ui/stores/types';
import BigN from 'bignumber.js';

export interface AiTransactionData {
Expand Down Expand Up @@ -86,7 +87,7 @@ const transformTransferData = (message: string): AiTransactionData => {
}
};

const transformSwapData = (message: string): AiTransactionData => {
const transformSwapData = (message: string, assetRegistryMap: AssetRegistryStore['assetRegistry']): AiTransactionData => {
const defaultResult: AiTransactionData = {
type: 'unknown'
};
Expand All @@ -110,13 +111,16 @@ const transformSwapData = (message: string): AiTransactionData => {
return defaultResult;
}

const chainAsset = assetRegistryMap[tokenFrom];
const decimals = _getAssetDecimals(chainAsset);

const data: Omit<SwapAiRequest, 'address'> = {
pair: {
slug: _parseAssetRefKey(tokenFrom, tokenTo),
from: tokenFrom,
to: tokenTo
},
fromAmount: new BigN(amount).shiftedBy(18).toString(),
fromAmount: new BigN(amount).shiftedBy(decimals).toString(),
slippage: (Number.parseInt(slippageTolerance)) / 100,
recipient: undefined,
isTestnet
Expand Down Expand Up @@ -168,7 +172,7 @@ const transformMintData = (message: string): AiTransactionData => {
}
};

export const transformAiMessageData = (message: string): AiTransactionData => {
export const transformAiMessageData = (message: string, assetRegistryMap: AssetRegistryStore['assetRegistry']): AiTransactionData => {
const isConfirmation = message.split('\n').some((line) => (line.startsWith('## IP') || line.startsWith('## Swap')) && line.includes('confirmation'));

if (isConfirmation) {
Expand All @@ -179,7 +183,7 @@ export const transformAiMessageData = (message: string): AiTransactionData => {
} else if (message.includes('asset minting')) {
return transformMintData(message);
} else if (message.includes('swap')) {
return transformSwapData(message);
return transformSwapData(message, assetRegistryMap);
} else {
return {
type: 'unknown'
Expand Down

0 comments on commit 8ebe427

Please sign in to comment.