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

feat: improved web poc #4

Merged
merged 12 commits into from
Jun 5, 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
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({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use optional chaining operator

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
Loading