Skip to content

Commit

Permalink
feat: improved web poc (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
BeroBurny authored Jun 5, 2024
1 parent 25a91f7 commit d6e892b
Show file tree
Hide file tree
Showing 13 changed files with 800 additions and 1,041 deletions.
6 changes: 4 additions & 2 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@
"type": "module",
"dependencies": {
"@floating-ui/dom": "1.6.5",
"@metamask/sdk": "^0.20.3",
"date-fns": "^3.6.0",
"highlight.js": "11.9.0",
"web3-utils": "^4.2.3"
"mipd": "^0.0.7",
"web3": "^4.9.0",
"web3-utils": "^4.3.0"
}
}
18 changes: 18 additions & 0 deletions web/src/lib/components/ConnectView.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script lang="ts">
import { getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
import Web3Modal from '$lib/components/Web3Modal.svelte';
const modalStore = getModalStore();
function handleConnect() {
const modal: ModalSettings = {
type: 'component',
component: { ref: Web3Modal },
title: 'Select wallet',
buttonTextCancel: 'close'
};
modalStore.trigger(modal);
}
</script>

<button class="btn variant-filled" on:click={handleConnect}> Connect Wallet </button>
22 changes: 17 additions & 5 deletions web/src/lib/components/RequestSendModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
ListBoxItem
} from '@skeletonlabs/skeleton';
import { hacks_getChainIcon } from '$lib/hacks';
import { fromWei, toWei } from 'web3-utils';
// Props
/** Exposes parent props to this component. */
Expand All @@ -24,7 +25,10 @@
let threshold: number;
let balances;
let tokenInfo;
function updateWhitelistedOnTokenChange() {
tokenInfo = $modalStore[0]?.value?.tokens.get(token);
balances = $modalStore[0]?.value?.balances?.get(token) ?? [];
whitelisted = balances.map((balance) => balance.chainId);
}
Expand All @@ -36,7 +40,13 @@
function onRequestQuota(): void {
if ($modalStore[0].response)
$modalStore[0].response({ token, network, whitelisted, amount, threshold });
$modalStore[0].response({
token,
network,
whitelisted,
amount: toWei(amount, tokenInfo.decimals),
threshold: threshold ? toWei(threshold, tokenInfo.decimals) : undefined
});
modalStore.close();
}
Expand Down Expand Up @@ -99,7 +109,9 @@
/>
</svelte:fragment>
{$modalStore[0].value.networks.get(balance.chainId).name}
<svelte:fragment slot="trail">{balance.balance}</svelte:fragment>
<svelte:fragment slot="trail">
{fromWei(balance.balance, tokenInfo.decimals)}
</svelte:fragment>
</ListBoxItem>
{/each}
</ListBox>
Expand All @@ -110,9 +122,9 @@
</div>
</article>
<footer class="modal-footer {parent.regionFooter}">
<button class="btn {parent.buttonNeutral}" on:click={parent.onClose}
>{parent.buttonTextCancel}</button
>
<button class="btn {parent.buttonNeutral}" on:click={parent.onClose}>
{parent.buttonTextCancel}
</button>
<button class="btn {parent.buttonPositive}" on:click={onRequestQuota}>Send Tokens</button>
</footer>
</div>
Expand Down
153 changes: 146 additions & 7 deletions web/src/lib/components/ResponseSendModal.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
<script lang="ts">
import type { SvelteComponent } from 'svelte';
import { CodeBlock, getModalStore } from '@skeletonlabs/skeleton';
import { getModalStore } from '@skeletonlabs/skeleton';
import { hacks_getQuota } from '$lib/hacks';
import { formatDuration } from '$lib/formatters';
import { fromWei, toHex } from 'web3-utils';
import { selectedProvider } from '$lib/stores/wallet';
import { type NonPayableCallOptions, Web3 } from 'web3';
import { erc20Abi } from '$lib/erc20.abi';
// Props
/** Exposes parent props to this component. */
Expand All @@ -12,6 +17,78 @@
const modalStore = getModalStore();
$: quota = hacks_getQuota($modalStore[0]?.value);
$: token = $modalStore[0]?.meta.tokens.get($modalStore[0]?.value.token);
$: console.log(token, $modalStore[0]);
const submitting: boolean[] = [];
const successful: boolean[] = [];
// TODO: there is not place for this over here! refactor it to somewhere
async function submitTransaction(quotaRecord: Object, index: number) {
try {
submitting[index] = true;
const [ownerAddress] = await $selectedProvider.provider.request({
method: 'eth_requestAccounts',
params: []
});
// Preparation /w questionable approach but will see for now
try {
await $selectedProvider.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: toHex(quotaRecord.sourceChain) }]
});
} catch (error) {
if (error.code === 4902) {
const network = $modalStore[0]?.meta.networks.get(quotaRecord.sourceChain);
await $selectedProvider.provider.request({
method: 'wallet_addEthereumChain',
params: [
{
chainName: network.name,
chainId: toHex(quotaRecord.sourceChain),
rpcUrls: network.rpcURLs
}
]
});
}
}
const web3 = new Web3($selectedProvider.provider);
// @ts-ignore // chainId is missing in web3js call options type
const callOptions: NonPayableCallOptions = { chainId: quotaRecord.sourceChain };
// Approval sniff etc...\
const erc20 = new web3.eth.Contract(erc20Abi, quotaRecord.sourceTokenAddress);
const allowed = await erc20.methods
.allowance(ownerAddress, quotaRecord.transaction.to)
.call(callOptions);
if (BigInt(quotaRecord.amount) > BigInt(allowed)) {
const approval = await erc20.methods
.approve(quotaRecord.transaction.to, quotaRecord.amount)
.send({
...callOptions,
from: ownerAddress
});
console.log(approval);
if (!approval.status) throw new Error('Not Approved!'); // To stop execution
}
// FINAL STEP!
const receipt = await web3.eth.sendTransaction(quotaRecord.transaction);
console.warn(`TX receipt: `, receipt);
successful[index] = true;
} catch (error) {
console.error(error);
submitting[index] = false;
}
}
// Base Classes
const cBase = 'card p-4 w-modal shadow-xl space-y-4';
Expand All @@ -24,15 +101,77 @@
<article>
{#await quota}
<p>...waiting</p>
{:then data}
<CodeBlock language="json" code={JSON.stringify(data, null, 2)}></CodeBlock>
{:then response}
<article class="container mx-auto p-4">
<ul class="space-y-4">
{#each response.data as data, index}
{@const network = $modalStore[0]?.meta.networks.get(data.sourceChain)}
{@const balance = $modalStore[0]?.meta.balances
.get(token.symbol)
.find(({ chainId }) => chainId === data.sourceChain)}
<li
class="bg-gray-200 dark:bg-gray-700 p-4 rounded-lg flex justify-between items-center mb-4"
>
<div class="flex items-center">
<img src={network.logoURI} alt="Source Chain Icon" class="w-8 h-8 mr-2" />
<div>
<p class="text-lg font-semibold text-black dark:text-white">
{fromWei(data.amount, token.decimals)}
{token.name} on {network.name}
</p>
<p class="text-sm text-gray-600 dark:text-gray-400">
Balance: {fromWei(balance.balance, token.decimals)}
{token.name}
</p>
<p class="text-sm text-gray-600 dark:text-gray-400">
Fee: {data.fee.amountUSD} USD
</p>
</div>
</div>
<div class="flex flex-col items-center justify-center h-full">
{#if !successful[index]}
<button
class="border border-blue-500 text-blue-500 text-xs px-2 py-1 rounded mb-1"
disabled={submitting[index]}
on:click={() => submitTransaction(data, index)}
>
Submit & Send
</button>
<p class="text-gray-400 text-xs">
Estimated Time {formatDuration(data.duration)}
</p>
{:else}
<svg
class="w-6 h-6 text-green-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
></path>
</svg>
{/if}
</div>
</li>
{/each}
</ul>
</article>
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
</article>
<!-- prettier-ignore -->
<footer class="modal-footer {parent.regionFooter}">
<button class="btn {parent.buttonNeutral}" on:click={parent.onClose}>{parent.buttonTextCancel}</button>
</footer>
<footer class="modal-footer flex justify-end space-x-4 mt-4 {parent.regionFooter}">
<button
class="btn bg-gray-300 dark:bg-gray-600 text-black dark:text-white py-2 px-4 rounded {parent.buttonNeutral}"
on:click={parent.onClose}
>
{parent.buttonTextCancel}
</button>
</footer>
</div>
{/if}
13 changes: 8 additions & 5 deletions web/src/lib/components/TokenList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
title: token.name,
buttonTextCancel: 'close',
value: { networks: data.raw.networks, balances: token.balances },
meta: { icon: token.logoURI, sybol: token.symbol }
meta: { icon: token.logoURI, sybol: token.symbol, decimals: token.decimals }
};
modalStore.trigger(modal);
}
Expand All @@ -40,12 +40,15 @@
modalStore.trigger(modal);
}
function handleTokenSending(value: Object) {
async function handleTokenSending(value: Object) {
const data = await promise;
const modal: ModalSettings = {
type: 'component',
component: { ref: ResponseSendModal },
title: 'Sending tokens',
value
title: 'Submit transactions',
value,
meta: data.raw
};
modalStore.trigger(modal);
}
Expand All @@ -71,7 +74,7 @@
<td><img class="size-8" src={token.logoURI} alt={`${token.logoURI}-LOGO`} /></td>
<td>{token.name}</td>
<td />
<td>{fromWei(token.total, 'ether')}</td>
<td>{fromWei(token.total, token.decimals)}</td>
</tr>
{/each}
</tbody>
Expand Down
2 changes: 1 addition & 1 deletion web/src/lib/components/TokenModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<tr>
<td><img class="size-7" src={hacks_getChainIcon(balance.chainId)} alt="" /></td>
<td>{$modalStore[0].value.networks.get(balance.chainId).name}</td>
<td>{fromWei(balance.balance, 'mwei')}</td>
<td>{fromWei(balance.balance, $modalStore[0].meta.decimals)}</td>
</tr>
{/each}
</tbody>
Expand Down
50 changes: 48 additions & 2 deletions web/src/lib/components/Web3Modal.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
<script lang="ts">
import { connect } from '$lib/stores/wallet';
import type { SvelteComponent } from 'svelte';
import { getModalStore } from '@skeletonlabs/skeleton';
import { providers, selectedProvider } from '$lib/stores/wallet';
import type { EIP6963ProviderDetail } from 'mipd';
// Props
/** Exposes parent props to this component. */
export let parent: SvelteComponent;
const modalStore = getModalStore();
function selectWallet(provider: EIP6963ProviderDetail): void {
selectedProvider.set(provider);
modalStore.close();
}
// Base Classes
const cBase = 'card p-4 w-modal shadow-xl space-y-4';
const cHeader = 'text-2xl font-bold';
</script>

<button class="btn variant-filled" on:click={() => connect()}> Connect Wallet </button>
{#if $modalStore[0]}
<div class="modal-example-form {cBase}">
<header class={cHeader}>{$modalStore[0].title ?? '(title missing)'}</header>
<article class="container mx-auto p-4">
<ul class="space-y-4">
{#each $providers as provider}
<li
on:click={() => selectWallet(provider)}
class="relative bg-white dark:bg-gray-800 p-4 rounded-lg shadow hover:bg-gray-100 dark:hover:bg-gray-700 hover:shadow-lg transition duration-300 flex items-center cursor-pointer"
>
<img src={provider.info.icon} alt="{provider.info.icon} Icon" class="w-12 h-12 mr-4" />
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{provider.info.name}
</h3>
<p class="text-gray-600 dark:text-gray-400">{provider.info.rdns}</p>
</div>
</li>
{/each}
</ul>
</article>
<footer class="modal-footer {parent.regionFooter}">
<button class="btn {parent.buttonNeutral}" on:click={parent.onClose}
>{parent.buttonTextCancel}</button
>
</footer>
</div>
{/if}
Loading

0 comments on commit d6e892b

Please sign in to comment.