Skip to content

Commit

Permalink
feat: provision wallet notice dialog (#99)
Browse files Browse the repository at this point in the history
* feat: query smart wallet auto-provision fee

* fix: remove extra purse updates and swingset query

* feat: onboard IST with elements button

* feat: provision wallet notice dialog

* fix: only show dialog when no smart wallet provisioned

* feat: rename makeOffer methods and make modal default
  • Loading branch information
samsiegart authored Apr 25, 2024
1 parent c3c837a commit 1329752
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { BasicModal, Button, Text } from '@interchain-ui/react';
import { useAgoric } from '../hooks';
import { stringifyValue } from '@agoric/web-components';
import { AssetKind } from '@agoric/ertp';
import { OnboardIstModal } from './OnboardIstModal';

const feeDecimals = 6;

const defaultContent = (fee?: bigint) => {
const prettyFee = stringifyValue(fee, AssetKind.NAT, feeDecimals);

return (
<Text fontSize="large">
To interact with contracts on the Agoric chain, a smart wallet must be
created for your account. You will need{' '}
<Text fontSize="large" as="span" fontWeight="$bold">
{prettyFee} IST
</Text>{' '}
to fund its provision which will be deposited into the reserve pool. Click
"Proceed" to provision wallet and submit transaction.
</Text>
);
};

export type Props = {
onClose: () => void;
proceed?: () => void;
mainContent?: (fee?: bigint) => JSX.Element;
};

export const ProvisionNoticeModal = ({
onClose,
proceed,
mainContent = defaultContent,
}: Props) => {
const { smartWalletProvisionFee, purses } = useAgoric();
const istPurse = purses?.find(p => p.brandPetname === 'IST');
const canProceed =
!smartWalletProvisionFee ||
(istPurse && istPurse.currentAmount.value >= smartWalletProvisionFee);

return (
<BasicModal
modalContentClassName="max-w-3xl"
title="Smart Wallet Required"
closeOnClickaway={false}
isOpen={proceed !== undefined}
onClose={onClose}
>
<div className="my-4">
{mainContent(smartWalletProvisionFee)}
<div className="my-4 flex justify-center gap-4">
{istPurse && (
<div className="flex items-center">
<Text fontSize="large">
IST Balance:{' '}
<Text as="span" fontSize="large" fontWeight="$bold">
{istPurse &&
stringifyValue(
istPurse.currentAmount.value,
AssetKind.NAT,
feeDecimals,
)}
</Text>
</Text>
</div>
)}
<OnboardIstModal />
</div>
</div>
<div className="flex justify-end gap-2 mt-6">
<Button intent="secondary" onClick={onClose}>
Go Back
</Button>
<Button onClick={proceed} disabled={!canProceed}>
Proceed
</Button>
</div>
</BasicModal>
);
};
12 changes: 4 additions & 8 deletions packages/react-components/src/lib/context/AgoricContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ import type {

export type PurseJSONState<T extends AssetKind> = {
brand: Brand;
/** The board ID for this purse's brand */
brandBoardId: string;
/** The board ID for the deposit-only facet of this purse */
depositBoardId?: string;
/** The petname for this purse's brand */
brandPetname: string;
/** The petname for this purse */
Expand All @@ -24,9 +20,6 @@ export type PurseJSONState<T extends AssetKind> = {
assetKind: AssetKind;
decimalPlaces?: number;
};
/** The purse's current balance */
value: AmountValue;
currentAmountSlots: unknown;
currentAmount: Amount<T>;
};

Expand All @@ -44,8 +37,11 @@ export type AgoricState = {
offerIdsToPublicSubscribers?: Record<string, Record<string, string>>;
isSmartWalletProvisioned?: boolean;
provisionSmartWallet?: AgoricWalletConnection['provisionSmartWallet'];
makeOffer?: AgoricWalletConnection['makeOffer'];
makeOfferWithoutModal?: AgoricWalletConnection['makeOffer'];
smartWalletProvisionFee?: bigint;
makeOffer?: (
...params: Parameters<AgoricWalletConnection['makeOffer']>
) => void;
};

export const AgoricContext = createContext<AgoricState>({});
4 changes: 4 additions & 0 deletions packages/react-components/src/lib/context/AgoricProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PropsWithChildren, useContext } from 'react';
import { NetworkConfig, NetworkContext } from './NetworkContext';
import { NetworkProvider } from './NetworkProvider';
import type { MainWalletBase, WalletConnectOptions } from '@cosmos-kit/core';
import type { Props as ProvisionNoticeProps } from '../components/ProvisionNoticeModal';

import '@interchain-ui/react/styles';

Expand All @@ -14,6 +15,7 @@ export type AgoricProviderProps = PropsWithChildren<{
defaultNetworkConfig: NetworkConfig;
walletConnectOptions?: WalletConnectOptions;
onConnectionError?: (e: unknown) => void;
provisionNoticeContent?: ProvisionNoticeProps['mainContent'];
}>;

export const AgoricProvider = (props: AgoricProviderProps) => {
Expand All @@ -29,6 +31,7 @@ const AgoricProviderInner = ({
walletConnectOptions,
children,
onConnectionError,
provisionNoticeContent,
}: AgoricProviderProps) => {
const { networkConfig } = useContext(NetworkContext);
assert(networkConfig, 'Network config missing from context');
Expand All @@ -53,6 +56,7 @@ const AgoricProviderInner = ({
<AgoricProviderLite
chainName={chainName}
onConnectionError={onConnectionError}
provisionNoticeContent={provisionNoticeContent}
>
{children}
</AgoricProviderLite>
Expand Down
35 changes: 33 additions & 2 deletions packages/react-components/src/lib/context/AgoricProviderLite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ import {
import { subscribeLatest } from '@agoric/notifier';
import type { ChainName } from 'cosmos-kit';
import type { AssetKind } from '@agoric/ertp/src/types';
import {
ProvisionNoticeModal,
type Props as ProvisionNoticeProps,
} from '../components/ProvisionNoticeModal';

export type AgoricProviderLiteProps = PropsWithChildren<{
chainName?: ChainName;
useCustomEndpoints?: boolean;
onConnectionError?: (e: unknown) => void;
provisionNoticeContent?: ProvisionNoticeProps['mainContent'];
}>;

/**
Expand All @@ -48,6 +53,7 @@ export const AgoricProviderLite = ({
onConnectionError = () => {},
chainName = 'agoric',
useCustomEndpoints = true,
provisionNoticeContent,
}: AgoricProviderLiteProps) => {
const [walletConnection, setWalletConnection] = useState<
AgoricWalletConnection | undefined
Expand All @@ -66,6 +72,9 @@ export const AgoricProviderLite = ({
const [smartWalletProvisionFee, setSmartWalletProvisionFee] = useState<
bigint | undefined
>(undefined);
const [postProvisionOffer, setPostProvisionOffer] = useState<
(() => void) | undefined
>(undefined);

const { status, client } = useWalletClient();
const chain = useChain(chainName);
Expand Down Expand Up @@ -206,6 +215,20 @@ export const AgoricProviderLite = ({
};
}, [status, chain.address, chainStorageWatcher]);

const checkSmartWalletProvisionAndMakeOffer =
isSmartWalletProvisioned !== undefined
? (...offerArgs: Parameters<AgoricWalletConnection['makeOffer']>) => {
if (isSmartWalletProvisioned) {
walletConnection?.makeOffer(...offerArgs);
return;
}
setPostProvisionOffer(() => () => {
walletConnection?.makeOffer(...offerArgs);
setPostProvisionOffer(undefined);
});
}
: undefined;

const state = {
address: chain.address,
chainName,
Expand All @@ -215,12 +238,20 @@ export const AgoricProviderLite = ({
purses,
offerIdsToPublicSubscribers,
isSmartWalletProvisioned,
makeOffer: walletConnection?.makeOffer,
makeOfferWithoutModal: walletConnection?.makeOffer,
provisionSmartWallet: walletConnection?.provisionSmartWallet,
makeOffer: checkSmartWalletProvisionAndMakeOffer,
smartWalletProvisionFee,
};

return (
<AgoricContext.Provider value={state}>{children}</AgoricContext.Provider>
<AgoricContext.Provider value={state}>
{children}
<ProvisionNoticeModal
mainContent={provisionNoticeContent}
onClose={() => setPostProvisionOffer(undefined)}
proceed={postProvisionOffer}
/>
</AgoricContext.Provider>
);
};

0 comments on commit 1329752

Please sign in to comment.