Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d879ff7
feat: osmosis swap messages
mateuszjasiuk Apr 15, 2025
472e93f
feat: hardcoded values for transparent osmosis swap
mateuszjasiuk Apr 22, 2025
ec2138a
feat: hardcoded values for shielded osmosis swap
mateuszjasiuk Apr 23, 2025
33c7cba
feat: osmosis calcs shiz wip
mateuszjasiuk Apr 29, 2025
6678d71
feat: query osmosis sqs for quote
mateuszjasiuk Apr 30, 2025
e2f3121
feat: correct osmosis swap data
mateuszjasiuk Apr 30, 2025
93453b6
feat: correct slippage and price
mateuszjasiuk May 2, 2025
4199570
feat: wire in swaps
mateuszjasiuk May 2, 2025
068de06
feat: use correct namada assets
mateuszjasiuk May 27, 2025
d183d19
swaps wip
mateuszjasiuk Jun 2, 2025
d0e34c1
fix: rebase errors
mateuszjasiuk Aug 8, 2025
14194fd
feat: wip channels
mateuszjasiuk Aug 11, 2025
b1dc7fb
feat: swapping UI
mateuszjasiuk Aug 14, 2025
ac349b6
chore: rebase cleanup
mateuszjasiuk Sep 19, 2025
7aa69f5
feat: ui
mateuszjasiuk Sep 22, 2025
b57df32
feat: UI assets selectors
mateuszjasiuk Sep 23, 2025
190c816
feat: UI display denominated values
mateuszjasiuk Sep 23, 2025
d736342
feat: UI display fiat values
mateuszjasiuk Sep 23, 2025
7a6475e
feat: persist last selected swap assets in local storage
mateuszjasiuk Sep 23, 2025
29642ae
feat: replace assets logic
mateuszjasiuk Sep 24, 2025
e06cc82
feat: validation and state improvements
mateuszjasiuk Sep 24, 2025
da1fe3b
feat: debounce the quote calls
mateuszjasiuk Sep 24, 2025
dc9b5c7
feat: improve validations for tokens without balance
mateuszjasiuk Sep 24, 2025
5041a82
feat: display swap details - fee
mateuszjasiuk Sep 24, 2025
d33a29c
feat: do not update outdated state in async call
mateuszjasiuk Sep 25, 2025
7aee983
feat: enable shielded swaps
mateuszjasiuk Sep 26, 2025
5c8f54b
feat: shielded swaps toast
mateuszjasiuk Sep 29, 2025
98ca15b
feat: shielded swaps animations and fixes
mateuszjasiuk Oct 1, 2025
f2128a7
feat: shielded swaps - prod ready refactor
mateuszjasiuk Oct 3, 2025
c1d288d
feat: shielded swaps - refactor cd
mateuszjasiuk Oct 7, 2025
605a490
feat: error handling
mateuszjasiuk Oct 7, 2025
cf5b0bb
feat: navigation
mateuszjasiuk Oct 7, 2025
0d016f8
feat: merge swap state with assets
mateuszjasiuk Oct 8, 2025
650df67
feat: disposable signer
mateuszjasiuk Oct 8, 2025
bb7cd95
feat: refund target
mateuszjasiuk Oct 9, 2025
3563a94
chore: cleanup
mateuszjasiuk Oct 9, 2025
88dff2e
feat: parse tx osmosis swap name in the extension
mateuszjasiuk Oct 10, 2025
6423d4d
feat: move connect wallet button to the calculations view
mateuszjasiuk Oct 10, 2025
6336392
chore: cleanup todos
mateuszjasiuk Oct 10, 2025
7acbb50
fix: rebase conflicts
mateuszjasiuk Nov 6, 2025
e2dc20d
chore: use beta sdkjs
mateuszjasiuk Nov 6, 2025
9e06daf
chore: cleanup
mateuszjasiuk Nov 6, 2025
cd9a7aa
fix: tests
mateuszjasiuk Nov 6, 2025
b8b8892
fix: code review comments
mateuszjasiuk Nov 7, 2025
fd2c475
fix: move animation and success view up
mateuszjasiuk Nov 7, 2025
5ead24e
fix: display Connect to osmosis when last connected chain is not osmosis
mateuszjasiuk Nov 7, 2025
09ab13d
feat: add link to osmosis
mateuszjasiuk Nov 7, 2025
2b3389c
fix: import amountMaxDecimalPlaces from utils into transfersource
neocybereth Nov 7, 2025
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,8 @@ yarn-error.log*
!.yarn/sdks
!.yarn/versions

.yalc
yalc.lock

# Local Netlify folder
.netlify
4 changes: 2 additions & 2 deletions apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@dao-xyz/borsh": "^5.1.5",
"@ledgerhq/hw-transport": "^6.31.4",
"@ledgerhq/hw-transport-webusb": "^6.29.4",
"@namada/sdk": "0.22.0",
"@namada/sdk": "0.23.0-beta.1",
"@zondax/ledger-namada": "^2.0.0",
"bignumber.js": "^9.1.1",
"buffer": "^6.0.3",
Expand All @@ -54,7 +54,7 @@
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.20.11",
"@namada/sdk-node": "^0.22.0",
"@namada/sdk-node": "0.23.0-beta.1",
"@svgr/webpack": "^6.3.1",
"@types/chrome": "^0.0.237",
"@types/firefox-webext-browser": "^94.0.1",
Expand Down
29 changes: 28 additions & 1 deletion apps/extension/src/Approvals/Commitment.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TxType } from "@namada/sdk";
import { IbcTransferProps, TxType } from "@namada/sdk";
import {
BondProps,
ClaimRewardsProps,
Expand All @@ -12,13 +12,17 @@ import {
} from "@namada/types";
import { shortenAddress } from "@namada/utils";
import { NamCurrency } from "App/Common/NamCurrency";
import * as J from "fp-ts/Json";
import { pipe } from "fp-ts/lib/function";
import * as O from "fp-ts/Option";
import { ReactNode } from "react";
import { FaVoteYea } from "react-icons/fa";
import { FaRegEye, FaWallet } from "react-icons/fa6";
import { GoStack } from "react-icons/go";
import { PiDotsNineBold } from "react-icons/pi";
import { isShieldedPool, parseTransferType, ShieldedPoolLabel } from "utils";
import { TransactionCard } from "./TransactionCard";
import { OsmosisSwapMemo } from "./types";

type CommitmentProps = {
commitment: CommitmentDetailProps;
Expand All @@ -37,6 +41,7 @@ const IconMap: Record<TxType, React.ReactNode> = {
[TxType.VoteProposal]: <FaVoteYea />,
[TxType.Batch]: <PiDotsNineBold />,
[TxType.ClaimRewards]: <GoStack />,
[TxType.OsmosisSwap]: <FaWallet />,
};

const TitleMap: Record<TxType, string> = {
Expand All @@ -51,6 +56,7 @@ const TitleMap: Record<TxType, string> = {
[TxType.VoteProposal]: "Vote",
[TxType.Batch]: "Batch",
[TxType.ClaimRewards]: "Claim Rewards",
[TxType.OsmosisSwap]: "Shielded Swap",
};

const formatAddress = (address: string): string =>
Expand Down Expand Up @@ -147,6 +153,27 @@ export const Commitment = ({
wrapperFeePayer
);
title = `${type} ${title}`;
} else if (commitment.txType === TxType.IBCTransfer) {
const ibcTx = commitment as CommitmentDetailProps<IbcTransferProps>;

// It's fine not to handle errors here as memo can be optional and not JSON at all
const maybeMemo = pipe(
O.fromNullable(ibcTx.memo),
O.map((memo) => J.parse(memo)),
O.map(O.fromEither),
O.flatten
);

const maybeOsmosisSwapMemo = pipe(
maybeMemo,
O.map(OsmosisSwapMemo.decode),
O.map(O.fromEither),
O.flatten
);

if (O.isSome(maybeOsmosisSwapMemo)) {
title = TitleMap[TxType.OsmosisSwap];
}
}

return (
Expand Down
61 changes: 61 additions & 0 deletions apps/extension/src/Approvals/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as t from "io-ts";
import { Message } from "router";

export enum TopLevelRoute {
Expand Down Expand Up @@ -40,3 +41,63 @@ export enum Status {
Pending,
Failed,
}

const NamadaOsmosisSwap = t.type({
overflow_receiver: t.string,
shielded_amount: t.string,
shielding_data: t.string,
});

const FinalMemoNamada = t.type({
osmosis_swap: NamadaOsmosisSwap,
});

const FinalMemo = t.type({
namada: FinalMemoNamada,
});

const OnFailedDelivery = t.type({
local_recovery_addr: t.string,
});

const RouteItem = t.type({
pool_id: t.string,
token_out_denom: t.string,
});

const Slippage = t.type({
min_output_amount: t.string,
});

const OsmosisSwapMsg = t.type({
final_memo: FinalMemo,
on_failed_delivery: OnFailedDelivery,
output_denom: t.string,
receiver: t.string,
route: t.array(RouteItem),
slippage: Slippage,
});

const Msg = t.type({
osmosis_swap: OsmosisSwapMsg,
});

const Wasm = t.type({
contract: t.string,
msg: Msg,
});

export const OsmosisSwapMemo = t.type({
wasm: Wasm,
});

export type NamadaOsmosisSwap = t.TypeOf<typeof NamadaOsmosisSwap>;
export type FinalMemoNamada = t.TypeOf<typeof FinalMemoNamada>;
export type FinalMemo = t.TypeOf<typeof FinalMemo>;
export type OnFailedDelivery = t.TypeOf<typeof OnFailedDelivery>;
export type RouteItem = t.TypeOf<typeof RouteItem>;
export type Slippage = t.TypeOf<typeof Slippage>;
export type OsmosisSwapMsg = t.TypeOf<typeof OsmosisSwapMsg>;
export type Msg = t.TypeOf<typeof Msg>;
export type Wasm = t.TypeOf<typeof Wasm>;
export type OsmosisSwapMemo = t.TypeOf<typeof OsmosisSwapMemo>;
4 changes: 2 additions & 2 deletions apps/namadillo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@keplr-wallet/types": "^0.12.136",
"@namada/chain-registry": "^1.5.2",
"@namada/indexer-client": "4.0.5",
"@namada/sdk-multicore": "0.22.0",
"@namada/sdk-multicore": "0.23.0-beta.1",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/query-core": "^5.40.0",
"@tanstack/react-query": "^5.40.0",
Expand Down Expand Up @@ -79,7 +79,7 @@
},
"devDependencies": {
"@eslint/js": "^9.9.1",
"@namada/sdk-node": "^0.22.0",
"@namada/sdk-node": "0.23.0-beta.1",
"@namada/vite-esbuild-plugin": "^1.0.1",
"@playwright/test": "^1.24.1",
"@svgr/webpack": "^6.5.1",
Expand Down
4 changes: 4 additions & 0 deletions apps/namadillo/src/App/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { StakingOverview } from "./Staking/StakingOverview";
import { StakingRewards } from "./Staking/StakingRewards";
import { StakingWithdrawModal } from "./Staking/StakingWithdrawModal";
import { Unstake } from "./Staking/Unstake";
import { SwapModule } from "./Swap/SwapModule";
import { SwitchAccountPanel } from "./SwitchAccount/SwitchAccountPanel";
import { TransactionDetails } from "./Transactions/TransactionDetails";
import { TransactionHistory } from "./Transactions/TransactionHistory";
Expand Down Expand Up @@ -99,6 +100,9 @@ export const MainRoutes = (): JSX.Element => {
<Route path={routes.maspUnshield} element={<div />} />
</Route>

{/* Swapping */}
<Route path={routes.swap} element={<SwapModule />} />

{/* Transaction History */}
{(features.namTransfersEnabled || features.ibcTransfersEnabled) && (
<Route>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { ActionButton } from "@namada/components";
type ConnectProviderButtonProps = {
onClick?: () => void;
disabled?: boolean;
text?: string;
};

export const ConnectProviderButton = ({
onClick,
disabled,
text,
}: ConnectProviderButtonProps): JSX.Element => {
return (
<ActionButton
Expand All @@ -18,7 +20,7 @@ export const ConnectProviderButton = ({
size="xs"
backgroundColor="white"
>
Select Address
{text || "Select Address"}
</ActionButton>
);
};
32 changes: 32 additions & 0 deletions apps/namadillo/src/App/Common/LedgerDeviceTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { routes } from "App/routes";
import { BsQuestionCircleFill } from "react-icons/bs";
import { Link, useNavigate } from "react-router-dom";
import { IconTooltip } from "./IconTooltip";

export const LedgerDeviceTooltip = (): JSX.Element => {
const navigate = useNavigate();
return (
<IconTooltip
className="absolute w-4 h-4 top-0 right-0 mt-4 mr-5"
icon={<BsQuestionCircleFill className="w-4 h-4 text-yellow" />}
text={
<span>
If your device is connected and the app is open, please go to{" "}
<Link
onClick={(e) => {
e.preventDefault();
navigate(routes.settingsLedger, {
state: { backgroundLocation: location },
});
}}
to={routes.settingsLedger}
className="text-yellow"
>
Settings
</Link>{" "}
and pair your device with Namadillo.
</span>
}
/>
);
};
100 changes: 100 additions & 0 deletions apps/namadillo/src/App/Common/SelectAssetModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Stack } from "@namada/components";
import { Search } from "App/Common/Search";
import { SelectModal } from "App/Common/SelectModal";
import { TokenCard } from "App/Common/TokenCard";
import { ConnectedWalletInfo } from "App/Transfer/ConnectedWalletInfo";
import { nativeTokenAddressAtom } from "atoms/chain/atoms";
import { applicationFeaturesAtom } from "atoms/settings/atoms";
import BigNumber from "bignumber.js";
import clsx from "clsx";
import { useAtomValue } from "jotai";
import { useMemo, useState } from "react";
import { twMerge } from "tailwind-merge";
import { Address, Asset, NamadaAsset } from "types";

type DisplayAmount = BigNumber;
type FiatAmount = BigNumber;
type SelectWalletModalProps = {
onClose: () => void;
onSelect: (address: Address) => void;
assets: Asset[];
walletAddress: string;
ibcTransfer?: "deposit" | "withdraw";
balances?: Record<Address, [DisplayAmount, FiatAmount?]>;
};

export const SelectAssetModal = ({
onClose,
onSelect,
assets,
walletAddress,
ibcTransfer,
balances,
}: SelectWalletModalProps): JSX.Element => {
const { namTransfersEnabled } = useAtomValue(applicationFeaturesAtom);
const nativeTokenAddress = useAtomValue(nativeTokenAddressAtom).data;

const [filter, setFilter] = useState("");

const filteredAssets = useMemo(() => {
return assets.filter(
(asset) =>
asset.name.toLowerCase().indexOf(filter.toLowerCase()) >= 0 ||
asset.symbol.toLowerCase().indexOf(filter.toLowerCase()) >= 0
);
}, [assets, filter]);

return (
<SelectModal title="Select Asset" onClose={onClose}>
<ConnectedWalletInfo walletAddress={walletAddress} />
<div className="my-4">
<Search placeholder="Search asset" onChange={setFilter} />
</div>
<Stack
as="ul"
gap={0}
className="max-h-[400px] overflow-auto dark-scrollbar pb-4 mr-[-0.5rem]"
>
{filteredAssets.map((asset) => {
// Fpr IbcTransfer(Deposits), we consider base denom as a token address.
const tokenAddress =
ibcTransfer === "deposit" ?
asset.base
: (asset as NamadaAsset).address;

const disabled =
!namTransfersEnabled && asset.address === nativeTokenAddress;
return (
<li key={asset.base} className="text-sm">
<button
onClick={() => {
onSelect(tokenAddress);
onClose();
}}
className={twMerge(
clsx(
"text-left px-4 py-2.5",
"w-full rounded-sm border border-transparent",
"hover:border-neutral-400 transition-colors duration-150",
{ "pointer-events-none opacity-50": disabled }
)
)}
disabled={disabled}
>
<TokenCard
asset={asset}
address={tokenAddress}
disabled={disabled}
balance={balances?.[tokenAddress]}
/>
</button>
</li>
);
})}
{filteredAssets.length === 0 && (
<p className="py-2 font-light">There are no available assets</p>
)}
</Stack>
</SelectModal>
);
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { SkeletonLoading } from "@namada/components";
import { EmptyResourceIcon } from "App/Transfer/EmptyResourceIcon";
import clsx from "clsx";
import { getAssetImageUrl } from "integrations/utils";
import { GoChevronDown } from "react-icons/go";
import { Asset } from "types";
import { EmptyResourceIcon } from "./EmptyResourceIcon";

type SelectedAssetProps = {
asset?: Asset;
Expand Down
2 changes: 1 addition & 1 deletion apps/namadillo/src/App/Common/SidebarMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type Props = {

export const SidebarMenuItem = ({ url, children }: Props): JSX.Element => {
const className = clsx(
"flex items-center gap-5 text-lg text-white",
"flex items-center gap-4 text-lg text-white",
"transition-colors duration-300 ease-out-quad hover:text-cyan",
{
"!text-neutral-500 pointer-events-none select-none": !url,
Expand Down
Loading