Skip to content

Commit 0f53a7d

Browse files
authored
feat: deposit IST with leap elements (#128)
1 parent fe2daa2 commit 0f53a7d

12 files changed

+3536
-369
lines changed

package.json

+6-4
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
"@agoric/web-components": "^0.15.0",
2929
"@agoric/zoe": "^0.26.2",
3030
"@endo/eventual-send": "^1.1.2",
31-
"@endo/init": "^1.0.4",
32-
"@endo/lockdown": "^1.0.4",
3331
"@headlessui/react": "^1.6.6",
32+
"@keplr-wallet/types": "^0.12.74",
33+
"@leapwallet/elements": "0.12.1",
3434
"@types/lodash-es": "^4.17.7",
3535
"@types/node": "^18.7.13",
3636
"@types/react": "^18.0.17",
@@ -39,6 +39,7 @@
3939
"@typescript-eslint/parser": "^5.35.1",
4040
"@vitejs/plugin-react": "^2.0.1",
4141
"autoprefixer": "^10.4.8",
42+
"buffer": "^6.0.3",
4243
"clsx": "^1.2.1",
4344
"eslint": "^8.22.0",
4445
"eslint-plugin-import": "^2.26.0",
@@ -55,11 +56,12 @@
5556
"react-loader-spinner": "~5.4.5",
5657
"react-toastify": "^9.0.8",
5758
"start-server-and-test": "^2.0.3",
59+
"ses": "^1.3.0",
5860
"tailwindcss": "^3.1.8",
5961
"typescript": "^4.6.4",
60-
"vite": "^3.2.7",
62+
"vite": "^5.1.6",
6163
"vite-tsconfig-paths": "^3.5.0",
62-
"vitest": "^0.23.4"
64+
"vitest": "^1.4.0"
6365
},
6466
"resolutions": {
6567
"**/@agoric/xsnap": "0.14.3-dev-9f085d3.0",

src/assets/svg/wallet.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const Wallet = () => (
2+
<svg
3+
width="16"
4+
height="14"
5+
viewBox="0 0 20 18"
6+
fill="current"
7+
stroke="current"
8+
xmlns="http://www.w3.org/2000/svg"
9+
>
10+
<path d="M18.0156 3.75H3.125C2.77969 3.75 2.5 3.47031 2.5 3.125C2.5 2.77969 2.77969 2.5 3.125 2.5H18.125C18.4703 2.5 18.75 2.22031 18.75 1.875C18.75 0.839453 17.9105 0 16.875 0H2.5C1.11914 0 0 1.11914 0 2.5V15C0 16.3809 1.11914 17.5 2.5 17.5H18.0156C19.1102 17.5 20 16.659 20 15.625V5.625C20 4.59102 19.1102 3.75 18.0156 3.75ZM16.25 11.875C15.5598 11.875 15 11.3152 15 10.625C15 9.93477 15.5598 9.375 16.25 9.375C16.9402 9.375 17.5 9.93477 17.5 10.625C17.5 11.3152 16.9402 11.875 16.25 11.875Z" />
11+
</svg>
12+
);
13+
14+
export default Wallet;

src/components/LiquidityModal.tsx

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { LiquidityModal, Tabs } from '@leapwallet/elements';
2+
import { useAtomValue } from 'jotai';
3+
import { chainConnectionAtom, displayFunctionsAtom } from 'store/app';
4+
import { useElementsWalletClient } from 'utils/elementsWalletClient';
5+
import WalletIcon from 'assets/svg/wallet';
6+
import type { Brand } from '@agoric/ertp/src/types';
7+
import { AssetSelector } from '@leapwallet/elements';
8+
9+
import '@leapwallet/elements/styles.css';
10+
11+
export enum Direction {
12+
deposit = 'DEPOSIT',
13+
withdraw = 'WITHDRAW',
14+
}
15+
16+
type Props = {
17+
selectedAsset: Brand | null;
18+
direction: Direction;
19+
};
20+
21+
const agoricChainId = 'agoric-3';
22+
23+
const chainIdForCollateralPetname = (petname?: string) => {
24+
switch (petname) {
25+
case 'IST':
26+
return agoricChainId;
27+
default:
28+
return undefined;
29+
}
30+
};
31+
32+
const assetForCollateralPetname = (petname?: string) => {
33+
switch (petname) {
34+
case 'IST':
35+
return ['symbol', 'IST'] as AssetSelector;
36+
default:
37+
return undefined;
38+
}
39+
};
40+
41+
const LeapLiquidityModal = ({ selectedAsset, direction }: Props) => {
42+
const chainConnection = useAtomValue(chainConnectionAtom);
43+
const elementsWalletClient = useElementsWalletClient();
44+
45+
const { displayBrandPetname, displayBrandIcon } =
46+
useAtomValue(displayFunctionsAtom) ?? {};
47+
48+
const collateralPetname =
49+
(selectedAsset ?? undefined) &&
50+
displayBrandPetname &&
51+
displayBrandPetname(selectedAsset);
52+
53+
const buttonMsg = `${direction} ${collateralPetname ?? 'FUNDS'}`;
54+
55+
const renderLiquidityButton = ({ onClick }: { onClick: () => void }) => {
56+
return (
57+
<button
58+
className="normal-case font-sans flex items-center gap-2 border-2 border-solid border-interGreen fill-interGreen text-interGreen rounded-md px-3 py-2 text-xs font-semibold bg-emerald-400 bg-opacity-0 hover:bg-opacity-10 active:bg-opacity-20 transition disabled:cursor-not-allowed"
59+
onClick={onClick}
60+
>
61+
<WalletIcon />
62+
{buttonMsg}
63+
</button>
64+
);
65+
};
66+
67+
return (
68+
<LiquidityModal
69+
renderLiquidityButton={renderLiquidityButton}
70+
theme="light"
71+
walletClientConfig={{
72+
userAddress: chainConnection?.address,
73+
walletClient: elementsWalletClient,
74+
connectWallet: (chainId?: string) => {
75+
return elementsWalletClient.connect(chainId);
76+
},
77+
}}
78+
defaultActiveTab={collateralPetname === 'IST' ? Tabs.SWAP : Tabs.TRANSFER}
79+
config={{
80+
icon:
81+
(displayBrandIcon && displayBrandIcon(selectedAsset)) ?? './IST.png',
82+
title: buttonMsg,
83+
subtitle: '',
84+
tabsConfig: {
85+
[Tabs.BRIDGE_USDC]: {
86+
enabled: false,
87+
},
88+
[Tabs.FIAT_ON_RAMP]: {
89+
enabled: false,
90+
},
91+
[Tabs.CROSS_CHAIN_SWAPS]: {
92+
enabled: true,
93+
defaults: {
94+
destinationChainId:
95+
chainIdForCollateralPetname(collateralPetname),
96+
destinationAssetSelector:
97+
assetForCollateralPetname(collateralPetname),
98+
},
99+
},
100+
[Tabs.SWAP]: {
101+
enabled: true,
102+
defaults: {
103+
destinationChainId:
104+
direction === Direction.deposit
105+
? agoricChainId
106+
: chainIdForCollateralPetname(collateralPetname),
107+
sourceAssetSelector: assetForCollateralPetname(collateralPetname),
108+
sourceChainId:
109+
direction === Direction.deposit
110+
? chainIdForCollateralPetname(collateralPetname)
111+
: agoricChainId,
112+
destinationAssetSelector:
113+
assetForCollateralPetname(collateralPetname),
114+
},
115+
},
116+
[Tabs.TRANSFER]: {
117+
enabled: true,
118+
defaults: {
119+
destinationChainId:
120+
direction === Direction.deposit
121+
? agoricChainId
122+
: chainIdForCollateralPetname(collateralPetname),
123+
sourceAssetSelector: assetForCollateralPetname(collateralPetname),
124+
sourceChainId:
125+
direction === Direction.deposit
126+
? chainIdForCollateralPetname(collateralPetname)
127+
: agoricChainId,
128+
},
129+
},
130+
},
131+
}}
132+
/>
133+
);
134+
};
135+
136+
export default LeapLiquidityModal;

src/components/ProvisionSmartWalletDialog.tsx

+62-17
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import { useEffect, useState } from 'react';
2-
import { rpcNodeAtom } from 'store/app';
31
import { useAtomValue } from 'jotai';
2+
import { useEffect, useState } from 'react';
43
import { querySwingsetParams } from 'utils/swingsetParams';
4+
import { displayFunctionsAtom, pursesAtom, rpcNodeAtom } from 'store/app';
55
import ActionsDialog from './ActionsDialog';
6-
7-
// Increment every time the current terms change.
8-
export const currentTermsIndex = 1;
6+
import LeapLiquidityModal, { Direction } from './LiquidityModal';
97

108
const useSmartWalletFeeQuery = (rpc?: string) => {
11-
const [smartWalletFee, setFee] = useState<bigint | null>(null);
9+
const [smartWalletFee, setFee] = useState<{
10+
fee: bigint;
11+
feeUnit: bigint;
12+
} | null>(null);
1213
const [error, setError] = useState<Error | null>(null);
1314

1415
useEffect(() => {
@@ -23,7 +24,8 @@ const useSmartWalletFeeQuery = (rpc?: string) => {
2324
const feeUnit = params.params.beansPerUnit.find(
2425
({ key }: { key: string }) => key === 'feeUnit'
2526
)?.beans;
26-
setFee(BigInt(beansPerSmartWallet) / BigInt(feeUnit));
27+
assert(feeUnit);
28+
setFee({ fee: BigInt(beansPerSmartWallet), feeUnit: BigInt(feeUnit) });
2729
} catch (e) {
2830
setError(e as Error);
2931
}
@@ -36,30 +38,72 @@ const useSmartWalletFeeQuery = (rpc?: string) => {
3638

3739
return { smartWalletFee, error };
3840
};
41+
3942
type Props = {
4043
onConfirm: () => void;
4144
isOpen: boolean;
4245
onClose: () => void;
4346
};
4447

45-
const ProvisionSmartWalletDialog = ({ onConfirm, isOpen, onClose }: Props) => {
48+
const ProvisionSmartWalletNoticeDialog = ({
49+
onConfirm,
50+
isOpen,
51+
onClose,
52+
}: Props) => {
4653
const rpc = useAtomValue(rpcNodeAtom);
4754
const { smartWalletFee, error: _smartWalletFeeError } =
4855
useSmartWalletFeeQuery(rpc);
4956

5057
const smartWalletFeeForDisplay = smartWalletFee
51-
? smartWalletFee + ' IST'
58+
? String(smartWalletFee.fee / smartWalletFee.feeUnit) + ' IST'
5259
: null;
5360

61+
const purses = useAtomValue(pursesAtom);
62+
const istPurse = purses?.find(p => p.brandPetname === 'IST');
63+
const { displayAmount, getDecimalPlaces } =
64+
useAtomValue(displayFunctionsAtom) ?? {};
65+
5466
const body = (
55-
<span>
56-
To interact with contracts on the Agoric chain, a smart wallet must be
57-
created for your account. As an anti-spam measure, you will need{' '}
58-
{smartWalletFeeForDisplay && <b>{smartWalletFeeForDisplay}</b>} to fund
59-
its provision which will be deposited into the reserve pool. Click
60-
&quot;Proceed&quot; to provision wallet and submit transaction.
61-
</span>
67+
<>
68+
<div>
69+
To interact with contracts on the Agoric chain, a smart wallet must be
70+
created for your account. You will need{' '}
71+
{smartWalletFeeForDisplay && <b>{smartWalletFeeForDisplay}</b>} to fund
72+
its provision which will be deposited into the reserve pool. Click
73+
&quot;Proceed&quot; to provision wallet and submit transaction.
74+
</div>
75+
<div className="my-4 flex justify-center gap-4">
76+
{istPurse && displayAmount && (
77+
<div className="flex items-center">
78+
<span>
79+
IST Balance: <b>{displayAmount(istPurse.currentAmount)}</b>
80+
</span>
81+
</div>
82+
)}
83+
{istPurse && (
84+
<LeapLiquidityModal
85+
selectedAsset={istPurse.brand}
86+
direction={Direction.deposit}
87+
/>
88+
)}
89+
</div>
90+
</>
6291
);
92+
const istDecimals =
93+
istPurse && getDecimalPlaces && getDecimalPlaces(istPurse.brand);
94+
95+
// "feeUnit" is observed to be 1000000000000n, so when "fee" is 1000000000000n
96+
// that means 1 IST (after dividing "fee" by "feeUnit"). To convert to uIST,
97+
// we then multiply by 10^6.
98+
const denominatedSmartWalletFee =
99+
istDecimals &&
100+
smartWalletFee &&
101+
(smartWalletFee.fee / smartWalletFee.feeUnit) * 10n ** BigInt(istDecimals);
102+
103+
const hasRequiredFee =
104+
denominatedSmartWalletFee &&
105+
istPurse !== undefined &&
106+
istPurse.currentAmount.value >= denominatedSmartWalletFee;
63107

64108
return (
65109
<ActionsDialog
@@ -73,8 +117,9 @@ const ProvisionSmartWalletDialog = ({ onConfirm, isOpen, onClose }: Props) => {
73117
}}
74118
onClose={onClose}
75119
initialFocusPrimary={true}
120+
primaryActionDisabled={!hasRequiredFee}
76121
/>
77122
);
78123
};
79124

80-
export default ProvisionSmartWalletDialog;
125+
export default ProvisionSmartWalletNoticeDialog;

src/installSesLockdown.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import { lockdown } from '@endo/lockdown';
1+
import 'ses';
22
import '@endo/eventual-send/shim.js'; // adds support needed by E
3+
import { Buffer } from 'buffer';
4+
5+
window.global ||= window;
6+
globalThis.Buffer = Buffer;
37

48
const consoleTaming = import.meta.env.DEV ? 'unsafe' : 'safe';
59

src/styles/globals.css

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@
1111
--color-primary: #c084fc;
1212
--color-primary-dark: #a855f7;
1313
--currentColor: #d1d5db;
14+
--color-inter-green: 0, 177, 166;
1415
}

0 commit comments

Comments
 (0)