diff --git a/packages/widget/package.json b/packages/widget/package.json
index c1cfd32c..9140ed42 100644
--- a/packages/widget/package.json
+++ b/packages/widget/package.json
@@ -27,7 +27,7 @@
},
"author": "Sygmaprotocol Product Team",
"dependencies": {
- "@buildwithsygma/sygma-sdk-core": "^2.6.0",
+ "@buildwithsygma/sygma-sdk-core": "^2.6.2",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/contracts": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
@@ -36,6 +36,8 @@
"@lit/reactive-element": "^2.0.3",
"@polkadot/api": "^10.11.2",
"@polkadot/extension-dapp": "^0.46.6",
+ "@polkadot/keyring": "^12.6.2",
+ "@polkadot/util": "^12.6.2",
"ethers": "5.7.2",
"events": "^3.3.0",
"lit": "3.0.0"
@@ -45,7 +47,7 @@
"@open-wc/testing-helpers": "^3.0.0",
"eslint": "^8.48.0",
"eslint-plugin-lit": "^1.9.1",
- "jsdom": "^23.2.0",
+ "happy-dom": "^13.3.1",
"lit-analyzer": "^2.0.3",
"rollup-plugin-visualizer": "^5.9.2",
"typescript": "5.2.2",
diff --git a/packages/widget/src/components/address-input/address-input.ts b/packages/widget/src/components/address-input/address-input.ts
new file mode 100644
index 00000000..cc2a01a9
--- /dev/null
+++ b/packages/widget/src/components/address-input/address-input.ts
@@ -0,0 +1,80 @@
+import { LitElement, html } from 'lit';
+import type { HTMLTemplateResult } from 'lit';
+import { customElement, property, state } from 'lit/decorators.js';
+import { ifDefined } from 'lit/directives/if-defined.js';
+import { Network } from '@buildwithsygma/sygma-sdk-core';
+import { when } from 'lit/directives/when.js';
+import { validateAddress } from '../../utils';
+import { styles } from './styles';
+
+@customElement('sygma-address-input')
+export class AddressInput extends LitElement {
+ static styles = styles;
+ @property({
+ type: String
+ })
+ address: string = '';
+
+ @property({ attribute: false })
+ onAddressChange: (address: string) => void = () => {};
+
+ @property({
+ type: String
+ })
+ network: Network = Network.EVM;
+
+ @state()
+ errorMessage: string | null = null;
+
+ connectedCallback(): void {
+ super.connectedCallback();
+ this.handleAddressChange(this.address);
+ }
+
+ private handleAddressChange = (value: string): void => {
+ const trimedValue = value.trim();
+
+ if (this.errorMessage) {
+ this.errorMessage = null;
+ }
+
+ if (!trimedValue) {
+ return;
+ }
+
+ this.errorMessage = validateAddress(trimedValue, this.network);
+
+ if (!this.errorMessage) {
+ void this.onAddressChange(trimedValue);
+ }
+ };
+
+ render(): HTMLTemplateResult {
+ return html` `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'sygma-address-input': AddressInput;
+ }
+}
diff --git a/packages/widget/src/components/address-input/index.ts b/packages/widget/src/components/address-input/index.ts
new file mode 100644
index 00000000..3f29279d
--- /dev/null
+++ b/packages/widget/src/components/address-input/index.ts
@@ -0,0 +1 @@
+export { AddressInput } from './address-input';
diff --git a/packages/widget/src/components/address-input/styles.ts b/packages/widget/src/components/address-input/styles.ts
new file mode 100644
index 00000000..f5778c64
--- /dev/null
+++ b/packages/widget/src/components/address-input/styles.ts
@@ -0,0 +1,51 @@
+import { css } from 'lit';
+
+export const styles = css`
+ .inputAddressSection {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ gap: 0.5rem;
+ min-height: 7.75rem; // TOO: remove this hardcoded value
+ }
+
+ .inputAddressContainer {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ min-height: 7.75rem; // TOO: remove this hardcoded value
+ gap: 0.5rem;
+ }
+
+ .inputAddress {
+ border-radius: 1.5rem;
+ border: 0.063rem solid var(--zinc-200);
+ font-size: 0.875rem;
+ text-align: center;
+ resize: none;
+ box-sizing: border-box;
+ overflow: hidden;
+ padding: 1rem;
+ }
+
+ .inputAddress:focus {
+ outline: none;
+ border: 0.063rem solid var(--zinc-200);
+ }
+
+ .error {
+ border-color: red;
+ }
+
+ .errorMessage {
+ color: red;
+ font-weight: 300;
+ font-size: 0.75rem;
+ }
+
+ .labelContainer {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ }
+`;
diff --git a/packages/widget/src/components/amount-selector/styles.ts b/packages/widget/src/components/amount-selector/styles.ts
index c893ab35..f60e2615 100644
--- a/packages/widget/src/components/amount-selector/styles.ts
+++ b/packages/widget/src/components/amount-selector/styles.ts
@@ -15,7 +15,7 @@ export const styles = css`
display: flex;
width: 100%;
justify-content: flex-start;
- color: #525252;
+ color: var(--neutral-600);
font-size: 14px;
font-weight: 500;
line-height: 20px; /* 142.857% */
@@ -30,7 +30,7 @@ export const styles = css`
.amountSelectorInput {
border: none;
- color: #525252;
+ color: var(--neutral-600);
font-size: 34px;
font-weight: 500;
line-height: 40px;
@@ -52,7 +52,7 @@ export const styles = css`
}
.maxButton {
- color: #2563eb;
+ color: var(--blue-600);
border: none;
background: none;
font-weight: 500;
diff --git a/packages/widget/src/components/index.ts b/packages/widget/src/components/index.ts
index 35b47082..67dd98b6 100644
--- a/packages/widget/src/components/index.ts
+++ b/packages/widget/src/components/index.ts
@@ -1,2 +1,3 @@
export { AmountSelector } from './amount-selector';
export { NetworkSelector } from './network-selector';
+export { AddressInput } from './address-input';
diff --git a/packages/widget/src/controllers/widget.ts b/packages/widget/src/controllers/widget.ts
index ca9a119b..d13f82ff 100644
--- a/packages/widget/src/controllers/widget.ts
+++ b/packages/widget/src/controllers/widget.ts
@@ -18,6 +18,7 @@ export class WidgetController implements ReactiveController {
public supportedSourceNetworks: Domain[] = [];
public supportedDestinationNetworks: Domain[] = [];
public supportedResources: Resource[] = [];
+ public destinatonAddress?: string = '';
//@ts-expect-error it will be used
private assetTransfer?: EVMAssetTransfer | SubstrateAssetTransfer;
@@ -126,4 +127,9 @@ export class WidgetController implements ReactiveController {
console.log('resource amount', amount);
this.resourceAmount = amount;
};
+
+ onDestinationAddressChange = (address: string): void => {
+ console.log('destination address', address);
+ this.destinatonAddress = address;
+ };
}
diff --git a/packages/widget/src/styles.ts b/packages/widget/src/styles.ts
index 5b009932..9ee30549 100644
--- a/packages/widget/src/styles.ts
+++ b/packages/widget/src/styles.ts
@@ -1,6 +1,17 @@
import { css } from 'lit';
export const styles = css`
+ :host {
+ --zinc-200: #e4e4e7;
+ --zinc-400: #a1a1aa;
+ --white: #fff;
+ --gray-100: #f3f4f6;
+ --neutral-600: #525252;
+ --primary-300: #a5b4fc;
+ --primary-500: #6366f1;
+ --blue-600: #2563eb;
+ }
+
@font-face {
font-family: 'Inter';
font-style: normal;
@@ -15,11 +26,10 @@ export const styles = css`
gap: 16px;
padding: 24px;
- width: 350px; /* TODO: remove these hardcoded values */
- height: 476px; /* TODO: ↑ */
+ width: 21.875rem; /* TODO: remove these hardcoded values */
border-radius: 12px;
- border: 1px solid #f3f4f6;
- background-color: #fff;
+ border: 1px solid var(--gray-100);
+ background-color: var(--white);
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
@@ -47,7 +57,7 @@ export const styles = css`
border-radius: 16px;
border: none;
- background-color: #a5b4fc;
+ background-color: var(--primary-300);
color: #ffffff;
width: 314px; /* TODO: remove these hardcoded values */
@@ -62,7 +72,7 @@ export const styles = css`
}
.actionButton:active {
- background-color: #6366f1;
+ background-color: var(--primary-500);
}
.actionButtonReady {
@@ -75,13 +85,13 @@ export const styles = css`
padding: 12px 20px;
border-radius: 16px;
- background-color: #6366f1;
+ background-color: var(--primary-500);
color: #ffffff;
border: none;
}
.actionButtonReady:active {
- background-color: #a5b4fc;
+ background-color: var(--primary-300);
}
.actionButtonReady:hover {
@@ -94,7 +104,7 @@ export const styles = css`
gap: 6px;
align-self: flex-start;
- color: #525252;
+ color: var(--neutral-600);
font-size: 12px;
line-height: 150%;
}
diff --git a/packages/widget/src/utils/index.ts b/packages/widget/src/utils/index.ts
index 84226984..887615e6 100644
--- a/packages/widget/src/utils/index.ts
+++ b/packages/widget/src/utils/index.ts
@@ -1,5 +1,9 @@
import type { HTMLTemplateResult } from 'lit';
import { html } from 'lit';
+import { decodeAddress, encodeAddress } from '@polkadot/keyring';
+import { hexToU8a, isHex } from '@polkadot/util';
+import { Network } from '@buildwithsygma/sygma-sdk-core';
+import { ethers } from 'ethers';
import {
baseNetworkIcon,
cronosNetworkIcon,
@@ -46,3 +50,31 @@ export const capitalize = (s: string): string => {
const rest = s.slice(1);
return `${firstLetter}${rest}`;
};
+
+export const validateSubstrateAddress = (address: string): boolean => {
+ try {
+ encodeAddress(isHex(address) ? hexToU8a(address) : decodeAddress(address));
+ return true;
+ } catch (error) {
+ return false;
+ }
+};
+
+export const validateAddress = (
+ address: string,
+ network: Network
+): string | null => {
+ switch (network) {
+ case Network.SUBSTRATE: {
+ const validPolkadotAddress = validateSubstrateAddress(address);
+ return validPolkadotAddress ? null : 'invalid Substrate address';
+ }
+ case Network.EVM: {
+ const isAddress = ethers.utils.isAddress(address);
+
+ return isAddress ? null : 'invalid Ethereum address';
+ }
+ default:
+ return 'unsupported network';
+ }
+};
diff --git a/packages/widget/src/widget.ts b/packages/widget/src/widget.ts
index 7c481c84..be57cc16 100644
--- a/packages/widget/src/widget.ts
+++ b/packages/widget/src/widget.ts
@@ -6,6 +6,7 @@ import { switchNetworkIcon, sygmaLogo } from './assets';
import { WidgetController } from './controllers/widget';
import './components/network-selector';
import './components/amount-selector';
+import './components/address-input';
import { Directions } from './components/network-selector/network-selector';
@customElement('sygmaprotocol-widget')
@@ -51,6 +52,14 @@ class SygmaProtocolWidget extends LitElement {
>
+