Skip to content

Commit

Permalink
feat: Adding swap method to checkout sdk (#2095)
Browse files Browse the repository at this point in the history
  • Loading branch information
alejoloaiza authored Aug 21, 2024
1 parent 8c41ad0 commit c32a4c9
Show file tree
Hide file tree
Showing 10 changed files with 785 additions and 5 deletions.
227 changes: 227 additions & 0 deletions packages/checkout/sdk-sample-app/src/components/Swap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { ChainId, Checkout, GetBalanceResult, TokenInfo } from '@imtbl/checkout-sdk';
import { Web3Provider } from '@ethersproject/providers';
import LoadingButton from './LoadingButton';
import { useEffect, useState } from 'react';
import { SuccessMessage, ErrorMessage, WarningMessage } from './messages';
import { Box, FormControl, TextInput } from '@biom3/react';
import React from 'react';

interface SwapProps {
checkout: Checkout | undefined;
provider: Web3Provider | undefined;
}

export default function Swap(props: SwapProps) {
const { provider, checkout } = props;

const [fromToken, setFromToken] = useState<TokenInfo | undefined>();
const [toToken, setToToken] = useState<TokenInfo | undefined>();
const [fromAmount, setFromAmount] = useState<string>('');
const [toAmount, setToAmount] = useState<string>('');
const [slippagePercent, setSlippagePercent] = useState<string>('0.1');
const [maxHops, setMaxHops] = useState<string>('2');
const [deadline, setDeadline] = useState<string>(() => {
const fifteenMinutesInSeconds = 15 * 60;
return Math.floor(Date.now() / 1000 + fifteenMinutesInSeconds).toString();
});

const [error, setError] = useState<any>(null);
const [loading, setLoading] = useState<boolean>(false);
const [success, setSuccess] = useState<boolean>(false);
const [validationError, setValidationError] = useState<string | null>(null);

const [fromTokenDecimals, setFromTokenDecimals] = useState<number>(18);
const [toTokenDecimals, setToTokenDecimals] = useState<number>(18);

const updateFromToken = (event: React.ChangeEvent<HTMLInputElement>) => {
setFromToken({ address: event.target.value, symbol: '', name: '', decimals: fromTokenDecimals });
setError(null);
};

const updateToToken = (event: React.ChangeEvent<HTMLInputElement>) => {
setToToken({ address: event.target.value, symbol: '', name: '', decimals: toTokenDecimals });
setError(null);
};

const updateFromTokenDecimals = (event: React.ChangeEvent<HTMLInputElement>) => {
const decimals = parseInt(event.target.value) || 18;
setFromTokenDecimals(decimals);
setFromToken(prevToken => prevToken ? { ...prevToken, decimals } : undefined);
setError(null);
};

const updateToTokenDecimals = (event: React.ChangeEvent<HTMLInputElement>) => {
const decimals = parseInt(event.target.value) || 18;
setToTokenDecimals(decimals);
setToToken(prevToken => prevToken ? { ...prevToken, decimals } : undefined);
setError(null);
};

const updateFromAmount = (event: React.ChangeEvent<HTMLInputElement>) => {
const newFromAmount = event.target.value;
setFromAmount(newFromAmount);
setError(null);
validateAmounts(newFromAmount, toAmount);
};

const updateToAmount = (event: React.ChangeEvent<HTMLInputElement>) => {
const newToAmount = event.target.value;
setToAmount(newToAmount);
setError(null);
validateAmounts(fromAmount, newToAmount);
};

const validateAmounts = (from: string, to: string) => {
if (from !== '' && to !== '') {
setValidationError('Please provide either From Amount or To Amount, not both.');
} else {
setValidationError(null);
}
};

const updateSlippagePercent = (event: React.ChangeEvent<HTMLInputElement>) => {
setSlippagePercent(event.target.value);
setError(null);
};

const updateMaxHops = (event: React.ChangeEvent<HTMLInputElement>) => {
setMaxHops(event.target.value);
setError(null);
};

const updateDeadline = (event: React.ChangeEvent<HTMLInputElement>) => {
setDeadline(event.target.value);
setError(null);
};

async function performSwap() {
if (validationError) {
setError(new Error(validationError));
return;
}
if (!checkout) {
console.error('missing checkout, please connect first');
return;
}
if (!provider) {
console.error('missing provider, please connect first');
return;
}
if (!fromToken || !toToken) {
console.error('missing token information');
return;
}
setError(null);
setLoading(true);
setSuccess(false);

try {
const result = await checkout.swap({
provider,
fromToken,
toToken,
fromAmount,
toAmount,
slippagePercent: slippagePercent.trim() !== '' ? parseFloat(slippagePercent) : undefined,
maxHops: maxHops.trim() !== '' ? parseInt(maxHops) : undefined,
deadline: deadline.trim() !== '' ? parseInt(deadline) : undefined,
});
console.log('Swap result:', result);
setSuccess(true);
setLoading(false);
} catch (err: any) {
setError(err);
setLoading(false);
console.log(err.message);
console.log(err.type);
console.log(err.data);
console.log(err.stack);
}
}

useEffect(() => {
setError(null);
setLoading(false);
setSuccess(false);
}, [checkout]);

return (
<div>
{!provider && <WarningMessage>Not connected.</WarningMessage>}
<Box>
<table>
<thead>
<tr>
<th>From Token Address</th>
<th>Decimals</th>
<th>To Token Address</th>
<th>Decimals</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<FormControl>
<TextInput name="fromToken" onChange={updateFromToken} />
</FormControl>
</td>
<td>
<FormControl >
<TextInput name="fromTokenDecimals" placeholder="18" onChange={updateFromTokenDecimals} />
</FormControl>
</td>
<td>
<FormControl>
<TextInput name="toToken" onChange={updateToToken} />
</FormControl>
</td>
<td>
<FormControl>
<TextInput name="toTokenDecimals" placeholder="18" onChange={updateToTokenDecimals} />
</FormControl>
</td>
</tr>
</tbody>
</table>
<FormControl>
<FormControl.Label>From Amount</FormControl.Label>
<TextInput onChange={updateFromAmount} value={fromAmount} />
</FormControl>
<FormControl>
<FormControl.Label>To Amount</FormControl.Label>
<TextInput onChange={updateToAmount} value={toAmount} />
</FormControl>
{validationError && <ErrorMessage>{validationError}</ErrorMessage>}
<FormControl>
<FormControl.Label>Slippage Percent</FormControl.Label>
<TextInput value={slippagePercent} onChange={updateSlippagePercent} />
</FormControl>
<FormControl>
<FormControl.Label>Max Hops</FormControl.Label>
<TextInput value={maxHops} onChange={updateMaxHops} />
</FormControl>
<FormControl>
<FormControl.Label>Deadline (minutes)</FormControl.Label>
<TextInput value={deadline} onChange={updateDeadline} />
</FormControl>
<Box
sx={{
marginTop: 'base.spacing.x4',
}}
>
<LoadingButton onClick={performSwap} loading={loading}>
Swap
</LoadingButton>
</Box>
{success && !error && (
<SuccessMessage>Swap successful. Check console for details.</SuccessMessage>
)}
{error && (
<ErrorMessage>
{error.message}. Check console logs for more details.
</ErrorMessage>
)}
</Box>
</div>
);
}
12 changes: 12 additions & 0 deletions packages/checkout/sdk-sample-app/src/pages/ConnectWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Environment } from '@imtbl/config';
import Provider from '../components/Provider';
import SendTransaction from '../components/SendTransaction';
import GetInjectedProviders from '../components/GetInjectedProviders';
import Swap from '../components/Swap';

export default function ConnectWidget() {
const [environment, setEnvironment] = useState(Environment.SANDBOX);
Expand Down Expand Up @@ -168,6 +169,17 @@ export default function ConnectWidget() {
Get injected providers
</Divider>
<GetInjectedProviders checkout={checkout} provider={provider} />

<Divider
sx={{
marginTop: 'base.spacing.x6',
marginBottom: 'base.spacing.x2',
}}
>
Swap
</Divider>
<Swap checkout={checkout} provider={provider} />

</div>
);
}
1 change: 1 addition & 0 deletions packages/checkout/sdk/src/errors/checkoutError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export enum CheckoutErrorType {
API_ERROR = 'API_ERROR',
ORDER_EXPIRED_ERROR = 'ORDER_EXPIRED_ERROR',
WIDGETS_SCRIPT_LOAD_ERROR = 'WIDGETS_SCRIPT_LOAD_ERROR',
APPROVAL_TRANSACTION_FAILED = 'APPROVAL_TRANSACTION_FAILED',
}

/**
Expand Down
Loading

0 comments on commit c32a4c9

Please sign in to comment.