Skip to content
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: 6 additions & 0 deletions docs/docs/03-sdk/03-class-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ sprinter.getAvailableChains().then(chains => {

Fetches the user's balances for specified tokens across multiple blockchains. If no tokens are specified, it fetches balances for all available tokens.

:::note

Method will always return native tokens under `ETH` key

:::

#### Parameters

- `account`: Targeted account address.
Expand Down
16 changes: 16 additions & 0 deletions packages/sdk/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
FetchOptions,
FungibleToken,
FungibleTokenBalance,
NativeTokenBalance,
Solution,
SolutionOptions,
SolutionResponse,
Expand Down Expand Up @@ -88,6 +89,21 @@ export async function getUserFungibleTokens(
return response.data;
}

export async function getUserNativeTokens(
address: Address,
{ baseUrl, signal }: FetchOptions = {},
): Promise<NativeTokenBalance[]> {
const url = new URL(
`/accounts/${address}/assets/native`,
baseUrl || BASE_URL,
);
const response = await fetch(url, { signal }).then(
(response) => response.json() as unknown as { data: NativeTokenBalance[] },
);

return response.data;
}

export async function getSolution(
{
account,
Expand Down
35 changes: 24 additions & 11 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getContractSolution,
getSupportedChains,
getUserFungibleTokens,
getUserNativeTokens,
setBaseUrl,
BASE_URL,
getContractCallSolution,
Expand All @@ -14,9 +15,9 @@ import type {
ContractSolutionOptions,
FetchOptions,
FungibleToken,
FungibleTokenBalance,
SolutionOptions,
SolutionResponse,
TokenBalance,
TokenSymbol,
} from "./types";

Expand Down Expand Up @@ -56,18 +57,23 @@ class Sprinter {
tokens?: FungibleToken[],
options: FetchOptions = {},
): Promise<{
[sybol: TokenSymbol]: { balances: FungibleTokenBalance[]; total: string };
[sybol: TokenSymbol]: { balances: TokenBalance[]; total: string };
}> {
const tokenList = tokens || (await this.getAvailableTokens(options));

const balances = await Promise.all(
tokenList.map((token) =>
getUserFungibleTokens(account, token.symbol).then((balances) => ({
symbol: token.symbol,
balances,
})),
const [balances, nativeTokens] = await Promise.all([
Promise.all(
tokenList.map((token) =>
getUserFungibleTokens(account, token.symbol, options).then(
(balances) => ({
symbol: token.symbol,
balances,
}),
),
),
),
);
getUserNativeTokens(account, options),
]);

return balances.reduce(
(previousValue, { symbol, balances }) => {
Expand All @@ -79,9 +85,16 @@ class Sprinter {
};
return previousValue;
},
{} as {
{
["ETH"]: {
total: nativeTokens
.reduce((prev, cur) => prev + BigInt(cur.balance), 0n)
.toString(),
balances: nativeTokens,
},
} as {
[symbol: TokenSymbol]: {
balances: FungibleTokenBalance[];
balances: TokenBalance[];
total: string;
};
},
Expand Down
6 changes: 5 additions & 1 deletion packages/sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ export interface Chain {
rpcurls: string[];
}

export interface FungibleTokenBalance {
export interface TokenBalance {
balance: string /* big number as string*/;
chainId: ChainID;
tokenDecimals: number;
}

export type FungibleTokenBalance = TokenBalance;

export type NativeTokenBalance = TokenBalance;

export interface SolutionOptions {
account: Address;
destinationChain: ChainID;
Expand Down
71 changes: 64 additions & 7 deletions web/src/lib/components/Portfolio.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,22 @@
const balances = $sprinter.getUserBalances($selectedAccount as Address);
const chains = $sprinter.getAvailableChains();

$: total = balances.then((b) =>
Object.values(b).reduce(
(p, c) => (p += Number(fromWei(c.total, c.balances[0].tokenDecimals))),
0
)
$: totalTokens = balances.then((b) =>
Object.keys(b)
.filter((sybols) => !['WETH', 'ETH'].includes(sybols))
.reduce((p, cKey) => {
const token = b[cKey];
return (p += Number(fromWei(token.total, token.balances[0].tokenDecimals)));
}, 0)
);

$: totalNative = balances.then((b) =>
Object.keys(b)
.filter((sybols) => ['WETH', 'ETH'].includes(sybols))
.reduce((p, cKey) => {
const token = b[cKey];
return (p += Number(fromWei(token.total, token.balances[0].tokenDecimals)));
}, 0)
);

async function handleListClick(index: number) {
Expand All @@ -35,22 +46,47 @@
};
modalStore.trigger(modal);
}

const ethLogo = 'https://scan.buildwithsygma.com/assets/icons/evm.svg';
async function handleNativeClick() {
const networks = await chains;
const selectedBalances = (await balances)['ETH'];

const modal: ModalSettings = {
type: 'component',
component: { ref: TokenModal },
title: 'Ethereum',
buttonTextCancel: 'close',
value: { networks, balances: selectedBalances.balances },
meta: { icon: ethLogo, sybol: 'ETH', decimals: 18 }
};
modalStore.trigger(modal);
}
</script>

<div class="w-[1024px] bg-gray-200 dark:bg-gray-800 flex flex-col items-center gap-4 rounded-xl">
<!-- Balance Section -->
<div class="w-full p-6 rounded-xl flex justify-between items-center relative py-9">
<div>
<div class="text-slate-800 dark:text-slate-200 text-lg font-normal text-left">Balance</div>
<div class="text-slate-800 dark:text-slate-200 text-4xl font-semibold">
{#await total}
<div class="text-slate-800 dark:text-slate-200 text-4xl font-semibold justify-start">
{#await totalTokens}
<div class="placeholder h-11 w-40" />
{:then result}
${result.toFixed(2)}
{:catch error}
- {JSON.stringify(error)}
{/await}
</div>
<div class="text-slate-800 dark:text-slate-200 text-4xl font-semibold justify-start pt-1">
{#await totalNative}
<div class="placeholder h-11 w-40" />
{:then result}
{result.toFixed(2)} ETH
{:catch error}
- {JSON.stringify(error)}
{/await}
</div>
</div>
<div
class="absolute right-0 top-0 bottom-0 w-[80%] bg-gradient-to-r from-transparent via-indigo-600 to-fuchsia-800 rounded-xl"
Expand Down Expand Up @@ -82,6 +118,27 @@
</tr>
{/each}
{:then [tokens, balances, chains]}
<tr
class="pt-2 bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer"
on:click={() => handleNativeClick()}
>
<td class="flex items-center gap-2 py-2">
<div
class="relative w-6 h-6 bg-gradient-to-b from-amber-500 to-amber-300 rounded-full overflow-hidden"
>
<img class="size-6" src={ethLogo} alt={`ETH-LOGO`} />
</div>
<div class="flex">
<div class="text-slate-800 dark:text-slate-200 text-base">Ethereum</div>
</div>
</td>
<td class="text-slate-700 dark:text-slate-300 text-base text-left">
{fromWei(balances['ETH'].total, 18)} ETH
</td>
<td class="text-slate-700 dark:text-slate-300 text-base">
<Facepile balances={balances['ETH'].balances} networks={chains} />
</td>
</tr>
{#each tokens as token, i}
<tr
class="pt-2 bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer"
Expand Down
13 changes: 11 additions & 2 deletions web/src/lib/components/SendTokensDrawer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,17 @@
import { selectedAccount } from '$lib/stores/wallet';
import type { Address } from '@chainsafe/sprinter-sdk';

const tokens = $sprinter.getAvailableTokens();
const allBalances = $sprinter.getUserBalances($selectedAccount as Address);
const tokens = $sprinter.getAvailableTokens($selectedAccount as Address).then((tokens) => [
...tokens,
{
addresses: [],
decimals: 18,
logoURI: 'https://scan.buildwithsygma.com/assets/icons/evm.svg',
name: 'Ethereum',
symbol: 'ETH'
}
]);
const allBalances = $sprinter.getUserBalances();
const chains = $sprinter.getAvailableChains();

const drawerStore = getDrawerStore();
Expand Down