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

Update send flow snap #3167

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "BkSVql1/bpU0SEcfVP+nOYRmMEUo6VOYgMPR6T0zGuk=",
"shasum": "o388f8Ok93eiNS56maRtdc2RLI4ieYlONqZY+ZpGSEY=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/examples/packages/browserify/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "Vrto5s9k5vDfqRSpjkAyEciAWEJhQ1yMmNoiWhENrUk=",
"shasum": "3GrrVE0mbpG4zyCONoukNum5N1ovmMJi7xafPJ+m9Hk=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/examples/packages/send-flow/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "rn9CAjAXYPvPMWcU5DDEr1+x3ccMwv7+uvxC60VK6+E=",
"shasum": "9YAT2soLTWY6yInzFfNkwCY5aD6+OTv9flf5fflyJyw=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import type { Account, Currency } from '../types';
* @property selectedCurrency - The selected currency to display.
* @property total - The total cost of the transaction.
* @property fees - The fees for the transaction.
* @property displayClearIcon - Whether to display the clear icon or not.
* @property flushToAddress - Whether to flush the address field or not.
* @property errors - The form errors.
*/
Expand All @@ -25,7 +24,6 @@ export type SendFlowProps = {
selectedCurrency: 'BTC' | '$';
total: Currency;
fees: Currency;
displayClearIcon: boolean;
flushToAddress?: boolean;
errors?: {
amount?: string;
Expand All @@ -43,7 +41,6 @@ export type SendFlowProps = {
* @param props.total - The total cost of the transaction.
* @param props.errors - The form errors.
* @param props.fees - The fees for the transaction.
* @param props.displayClearIcon - Whether to display the clear icon or not.
* @param props.flushToAddress - Whether to flush the address field or not.
* @returns The SendFlow component.
*/
Expand All @@ -53,7 +50,6 @@ export const SendFlow: SnapComponent<SendFlowProps> = ({
selectedCurrency,
total,
fees,
displayClearIcon,
flushToAddress,
errors,
}) => {
Expand All @@ -66,7 +62,6 @@ export const SendFlow: SnapComponent<SendFlowProps> = ({
accounts={accounts}
selectedCurrency={selectedCurrency}
flushToAddress={flushToAddress}
displayClearIcon={displayClearIcon}
errors={errors}
/>
<TransactionSummary fees={fees} total={total} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import {
Icon,
Image,
Input,
AddressInput,
Text,
type SnapComponent,
} from '@metamask/snaps-sdk/jsx';

import { AccountSelector } from './AccountSelector';
import btcIcon from '../images/btc.svg';
import jazzicon3 from '../images/jazzicon3.svg';
import type { Account, SendFormErrors } from '../types';

/**
Expand All @@ -22,15 +22,13 @@ import type { Account, SendFormErrors } from '../types';
* @property accounts - The available accounts.
* @property errors - The form errors.
* @property selectedCurrency - The selected currency to display.
* @property displayClearIcon - Whether to display the clear icon or not.
* @property flushToAddress - Whether to flush the address field or not.
*/
export type SendFormProps = {
selectedAccount: string;
accounts: Account[];
errors?: SendFormErrors;
selectedCurrency: 'BTC' | '$';
displayClearIcon: boolean;
flushToAddress?: boolean;
};

Expand All @@ -42,7 +40,6 @@ export type SendFormProps = {
* @param props.accounts - The available accounts.
* @param props.errors - The form errors.
* @param props.selectedCurrency - The selected currency to display.
* @param props.displayClearIcon - Whether to display the clear icon or not.
* @param props.flushToAddress - Whether to flush the address field or not.
* @returns The SendForm component.
*/
Expand All @@ -51,7 +48,6 @@ export const SendForm: SnapComponent<SendFormProps> = ({
accounts,
errors,
selectedCurrency,
displayClearIcon,
flushToAddress,
}) => (
<Form name="sendForm">
Expand All @@ -69,21 +65,12 @@ export const SendForm: SnapComponent<SendFormProps> = ({
</Box>
</Field>
<Field label="To account" error={errors?.to}>
<Box>
<Image src={jazzicon3} />
</Box>
<Input
<AddressInput
name="to"
chainId="eip155:0"
placeholder="Enter receiving address"
value={flushToAddress ? '' : undefined}
/>
{displayClearIcon && (
Copy link
Member

Choose a reason for hiding this comment

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

Why did you remove this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The AddressInput component has a clear icon built-in.

<Box>
<Button name="clear">
<Icon name="close" color="primary" />
</Button>
</Box>
)}
</Field>
</Form>
);
11 changes: 0 additions & 11 deletions packages/examples/packages/send-flow/src/images/jazzicon3.svg

This file was deleted.

2 changes: 0 additions & 2 deletions packages/examples/packages/send-flow/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ export const onUserInput: OnUserInputHandler = async ({
selectedCurrency={selectedCurrency}
total={total}
fees={fees}
displayClearIcon={Boolean(sendForm.to) && sendForm.to !== ''}
errors={formErrors}
/>
),
Expand All @@ -138,7 +137,6 @@ export const onUserInput: OnUserInputHandler = async ({
total={total}
fees={fees}
flushToAddress={true}
displayClearIcon={false}
errors={formErrors}
/>
),
Expand Down
1 change: 0 additions & 1 deletion packages/examples/packages/send-flow/src/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export async function generateSendFlow({
selectedCurrency="BTC"
total={{ amount: 0, fiat: 0 }}
fees={fees}
displayClearIcon={false}
/>
),
context: {
Expand Down
2 changes: 1 addition & 1 deletion packages/snaps-controllers/coverage.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"branches": 93.31,
"branches": 93.35,
"functions": 97.05,
"lines": 98.25,
"statements": 97.98
Expand Down
44 changes: 44 additions & 0 deletions packages/snaps-controllers/src/interface/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
Selector,
Card,
SelectorOption,
AddressInput,
} from '@metamask/snaps-sdk/jsx';

import { assertNameIsUnique, constructState, getJsxInterface } from './utils';
Expand Down Expand Up @@ -283,6 +284,49 @@ describe('constructState', () => {
});
});

it('handles root level AddressInput with value', () => {
const element = (
<Box>
<AddressInput name="foo" chainId="eip155:1" value="0x123" />
</Box>
);

const result = constructState({}, element);
expect(result).toStrictEqual({
foo: 'eip155:1:0x123',
});
});

it('handles root level AddressInput without value', () => {
const element = (
<Box>
<AddressInput name="foo" chainId="eip155:1" />
</Box>
);

const result = constructState({}, element);
expect(result).toStrictEqual({
foo: null,
});
});

it('handles AddressInput in forms', () => {
const element = (
<Box>
<Form name="form">
<Field label="foo">
<AddressInput name="foo" chainId="eip155:1" />
</Field>
</Form>
</Box>
);

const result = constructState({}, element);
expect(result).toStrictEqual({
form: { foo: null },
});
});

it('sets default value for root level dropdown', () => {
const element = (
<Box>
Expand Down
26 changes: 21 additions & 5 deletions packages/snaps-controllers/src/interface/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
RadioElement,
SelectorElement,
SelectorOptionElement,
AddressInputElement,
} from '@metamask/snaps-sdk/jsx';
import { isJSXElementUnsafe } from '@metamask/snaps-sdk/jsx';
import {
Expand All @@ -25,6 +26,7 @@ import {
getJsxElementFromComponent,
walkJsx,
} from '@metamask/snaps-utils';
import { toCaipAccountId, parseCaipChainId } from '@metamask/utils';

/**
* Get a JSX element from a component or JSX element. If the component is a
Expand Down Expand Up @@ -70,7 +72,8 @@ function constructComponentSpecificDefaultState(
| DropdownElement
| RadioGroupElement
| CheckboxElement
| SelectorElement,
| SelectorElement
| AddressInputElement,
) {
switch (element.type) {
case 'Dropdown': {
Expand Down Expand Up @@ -111,12 +114,22 @@ function getComponentStateValue(
| DropdownElement
| RadioGroupElement
| CheckboxElement
| SelectorElement,
| SelectorElement
| AddressInputElement,
) {
switch (element.type) {
case 'Checkbox':
return element.props.checked;

case 'AddressInput': {
if (!element.props.value) {
return null;
}

// Construct CAIP-10 Id
const { namespace, reference } = parseCaipChainId(element.props.chainId);
return toCaipAccountId(namespace, reference, element.props.value);
}
default:
return element.props.value;
}
Expand All @@ -138,7 +151,8 @@ function constructInputState(
| RadioGroupElement
| FileInputElement
| CheckboxElement
| SelectorElement,
| SelectorElement
| AddressInputElement,
form?: string,
) {
const oldStateUnwrapped = form ? (oldState[form] as FormState) : oldState;
Expand Down Expand Up @@ -196,7 +210,8 @@ export function constructState(
component.type === 'RadioGroup' ||
component.type === 'FileInput' ||
component.type === 'Checkbox' ||
component.type === 'Selector')
component.type === 'Selector' ||
component.type === 'AddressInput')
) {
const formState = newState[currentForm.name] as FormState;
assertNameIsUnique(formState, component.props.name);
Expand All @@ -215,7 +230,8 @@ export function constructState(
component.type === 'RadioGroup' ||
component.type === 'FileInput' ||
component.type === 'Checkbox' ||
component.type === 'Selector'
component.type === 'Selector' ||
component.type === 'AddressInput'
) {
assertNameIsUnique(newState, component.props.name);
newState[component.props.name] = constructInputState(oldState, component);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ describe('snap_createInterface', () => {
error: {
code: -32602,
message:
'Invalid params: At path: ui -- Expected type to be one of: "Address", "Bold", "Box", "Button", "Copyable", "Divider", "Dropdown", "RadioGroup", "Field", "FileInput", "Form", "Heading", "Input", "Image", "Italic", "Link", "Row", "Spinner", "Text", "Tooltip", "Checkbox", "Card", "Icon", "Selector", "Section", "Avatar", "Banner", "Skeleton", "Container", but received: undefined.',
'Invalid params: At path: ui -- Expected type to be one of: "Address", "AddressInput", "Bold", "Box", "Button", "Copyable", "Divider", "Dropdown", "RadioGroup", "Field", "FileInput", "Form", "Heading", "Input", "Image", "Italic", "Link", "Row", "Spinner", "Text", "Tooltip", "Checkbox", "Card", "Icon", "Selector", "Section", "Avatar", "Banner", "Skeleton", "Container", but received: undefined.',
stack: expect.any(String),
},
id: 1,
Expand Down Expand Up @@ -191,7 +191,7 @@ describe('snap_createInterface', () => {
error: {
code: -32602,
message:
'Invalid params: At path: ui.props.children.props.children -- Expected type to be one of: "Input", "Dropdown", "RadioGroup", "FileInput", "Checkbox", "Selector", but received: "Copyable".',
'Invalid params: At path: ui.props.children.props.children -- Expected type to be one of: "AddressInput", "Input", "Dropdown", "RadioGroup", "FileInput", "Checkbox", "Selector", but received: "Copyable".',
stack: expect.any(String),
},
id: 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AddressInput } from './AddressInput';

describe('AddressInput', () => {
it('renders an address input', () => {
const result = <AddressInput name="address" chainId="eip155:1" />;

expect(result).toStrictEqual({
type: 'AddressInput',
props: {
name: 'address',
chainId: 'eip155:1',
},
key: null,
});
});
});
Loading
Loading