Skip to content

Commit

Permalink
feat: unstoppable domains (#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
JackHamer09 authored Dec 9, 2022
1 parent b05e227 commit 89de2f4
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 10 deletions.
1 change: 1 addition & 0 deletions environments/.env.goerli
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ RAMP_MAINNET_HOST_API_KEY=j3b3oszn2vsr4qkz4att67zrucm6m6yjpfvxvtyy
UTORG_MAINNET_SID=zksynkio
MOONPAY_MAINNET_API_PUBLIC_KEY=pk_live_cS0AkN9LR0qquMUk9w4JGsaeoplwEuD
MIXPANEL_TOKEN=5600dbc62a772c26f25b8df97ee54701
UNS_KEY=356a3eb3-d413-490a-94ac-4c9bb312ff49
FIREBASE_FUNCTIONS_BASE_URL=https://us-central1-zksync-vue.cloudfunctions.net/
1 change: 1 addition & 0 deletions environments/.env.goerli-beta
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ RAMP_MAINNET_HOST_API_KEY=j3b3oszn2vsr4qkz4att67zrucm6m6yjpfvxvtyy
UTORG_MAINNET_SID=zksynkio
MOONPAY_MAINNET_API_PUBLIC_KEY=pk_live_cS0AkN9LR0qquMUk9w4JGsaeoplwEuD
MIXPANEL_TOKEN=5600dbc62a772c26f25b8df97ee54701
UNS_KEY=356a3eb3-d413-490a-94ac-4c9bb312ff49
FIREBASE_FUNCTIONS_BASE_URL=https://us-central1-zksync-vue.cloudfunctions.net/
1 change: 1 addition & 0 deletions environments/.env.mainnet
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ RAMP_MAINNET_HOST_API_KEY=j3b3oszn2vsr4qkz4att67zrucm6m6yjpfvxvtyy
UTORG_MAINNET_SID=zksynkio
MOONPAY_MAINNET_API_PUBLIC_KEY=pk_live_cS0AkN9LR0qquMUk9w4JGsaeoplwEuD
MIXPANEL_TOKEN=d6ce0bc1434361893bfa0084a601710c
UNS_KEY=356a3eb3-d413-490a-94ac-4c9bb312ff49
FIREBASE_FUNCTIONS_BASE_URL=https://us-central1-zksync-vue.cloudfunctions.net/
8 changes: 8 additions & 0 deletions src/assets/style/components/_walletAddress.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
color: $red;
font-size: 12px;
}

.domainAddress {
position: absolute;
left: 0;
top: calc(100% + 3px);
width: 100%;
font-size: 12px;
}
}

&.clickablePicture {
Expand Down
27 changes: 20 additions & 7 deletions src/blocks/Transaction.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,14 @@

<template v-if="displayAddressInput">
<div :class="[isDeposit ? '_margin-top-05' : '_margin-top-1']" class="inputLabel">Address</div>
<address-input ref="addressInput" v-model="inputtedAddress" @enter="commitTransaction()" />
<address-input
ref="addressInput"
v-model="inputtedAddress"
:token="chosenToken ? chosenToken : undefined"
@enter="commitTransaction()"
/>
<block-choose-contact
:address="inputtedAddress"
:address="inputtedAddressWithDomain"
:display-own-address="displayOwnAddress"
class="_margin-top-05"
@chosen="chooseAddress($event)"
Expand Down Expand Up @@ -466,6 +471,10 @@ export default Vue.extend({
cpkStatus(): ZkCPKStatus {
return this.$store.getters["zk-wallet/cpk"];
},
inputtedAddressWithDomain(): string {
const domainAddress = this.$store.getters["uns/getDomain"](this.inputtedAddress, this.chosenToken);
return domainAddress || this.inputtedAddress;
},
},
watch: {
inputtedAmount: {
Expand All @@ -480,8 +489,9 @@ export default Vue.extend({
}
},
},
inputtedAddress(val) {
this.$store.dispatch("zk-transaction/setAddress", val);
inputtedAddressWithDomain(val) {
const domainAddress = this.$store.getters["uns/getDomain"](val, this.chosenToken);
this.$store.dispatch("zk-transaction/setAddress", domainAddress || val);
},
contentHash(val) {
this.$store.commit("zk-transaction/setContentHash", val);
Expand Down Expand Up @@ -534,7 +544,10 @@ export default Vue.extend({
async checkTransfer(): Promise<boolean> {
const transferWithdrawWarning = localStorage.getItem(warningCanceledKey);
if (transferWithdrawWarning || getAddress(this.inputtedAddress) === this.$store.getters["zk-account/address"]) {
if (
transferWithdrawWarning ||
getAddress(this.inputtedAddressWithDomain) === this.$store.getters["zk-account/address"]
) {
return true;
}
Expand Down Expand Up @@ -648,7 +661,7 @@ export default Vue.extend({
break;
case "Withdraw":
this.$analytics.track(
(getAddress(this.inputtedAddress) === this.$store.getters["zk-account/address"]
(getAddress(this.inputtedAddressWithDomain) === this.$store.getters["zk-account/address"]
? "l1_withdraw"
: "l1_transfer") + status,
{
Expand All @@ -665,7 +678,7 @@ export default Vue.extend({
},
async checkInputtedAccountUnlocked(): Promise<boolean> {
const syncProvider: RestProvider = await this.$store.dispatch("zk-provider/requestProvider");
const state = await syncProvider.getState(this.inputtedAddress);
const state = await syncProvider.getState(this.inputtedAddressWithDomain);
return state.id != null;
},
async unlockToken(unlimited = false) {
Expand Down
56 changes: 53 additions & 3 deletions src/components/AddressInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@
class="walletAddress"
maxlength="100"
data-cy="address_block_wallet_address_input"
placeholder="0x address"
placeholder="0x address or domain"
spellcheck="false"
type="text"
@keyup.enter="$emit('enter')"
@change="$emit('change', $event)"
@input="getDomainAddress"
/>

<transition name="fadeFast">
<div v-if="unsDomain" class="text-xs text-left flex domainAddress">
<img height="20" width="20" src="/images/UnsLogo.png" alt="Unstoppable Domains Logo" />
{{ domainSubText }}
</div>
<div v-if="error" class="errorText" data-cy="address_block_error_message">{{ error }}</div>
</transition>
</div>
Expand All @@ -26,7 +32,7 @@

<script lang="ts">
import Vue, { PropOptions } from "vue";
import { Address } from "zksync/build/types";
import { Address, TokenSymbol } from "zksync/build/types";
import { validateAddress } from "@matterlabs/zksync-nuxt-core/utils";
export default Vue.extend({
Expand All @@ -36,23 +42,49 @@ export default Vue.extend({
default: "",
required: false,
} as PropOptions<Address>,
token: {
type: String,
required: false,
default: "ETH",
} as PropOptions<TokenSymbol>,
},
data() {
return {
inputtedWallet: this.value ?? "",
domainFetchingInProgress: false,
};
},
computed: {
isValid(): boolean {
return validateAddress(this.inputtedWallet);
return validateAddress(this.inputtedWallet) || this.isValidDomain;
},
error(): string {
if (this.domainFetchingInProgress) {
return "";
}
if (this.inputtedWallet && !this.isValid) {
return "Invalid address";
} else {
return "";
}
},
isValidDomain(): boolean {
return !!this.getDomain && !this.domainFetchingInProgress;
},
getDomain(): string | null {
return this.$store.getters["uns/getDomain"](this.inputtedWallet, this.token);
},
domainSubText(): string {
const domain = this.unsDomain;
if (domain) {
return domain.substring(0, 6) + "..." + domain.substring(domain.length - 6, domain.length);
} else {
return "";
}
},
unsDomain(): string | null {
return this.getDomain;
},
},
watch: {
inputtedWallet(val) {
Expand All @@ -63,18 +95,36 @@ export default Vue.extend({
}
this.$emit("input", this.isValid ? val : "");
},
getDomain() {
this.$emit("input", this.isValid ? this.inputtedWallet : "");
},
value(val) {
if (this.isValid || (!this.isValid && !!val)) {
this.inputtedWallet = val;
}
},
token() {
this.getDomainAddress();
},
},
methods: {
focusInput(): void {
if (this.$refs.input) {
(this.$refs.input as HTMLElement).focus();
}
},
async getDomainAddress() {
if (!this.isValidDomain) {
try {
this.domainFetchingInProgress = true;
await this.$store.dispatch("uns/lookupDomain", { address: this.inputtedWallet });
} catch (error) {
console.warn("UNS lookup failed", error);
} finally {
this.domainFetchingInProgress = false;
}
}
},
},
});
</script>
Binary file added src/static/images/UnsLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
83 changes: 83 additions & 0 deletions src/store/uns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { actionTree, getAccessorType, getterTree, mutationTree } from "typed-vuex";
import Vue from "vue";

const resolutionService = "https://resolve.unstoppabledomains.com/domains/";
const tldAPI = "https://resolve.unstoppabledomains.com/supported_tlds";
const ethKey = "crypto.ETH.address";

type UnsState = {
supportedTlds: Array<string>;
domainData: Map<string, any>;
};
export const state = (): UnsState => ({
supportedTlds: [],
domainData: Object.create(null),
});
export const getters = getterTree(state, {
getDomain:
(state: UnsState) =>
(address: string, ticker: string = "ETH") => {
const key = "crypto." + ticker + ".address";
const domainWithTicker = state.domainData[address]?.[key];
return domainWithTicker || state.domainData[address]?.[ethKey];
},
getSupportedTld: (state: UnsState) => state.supportedTlds,
getDomainData: (state: UnsState) => state.domainData,
});

export const mutations = mutationTree(state, {
setSupportedTlds(state: UnsState, supportedTlds: string[]): void {
Vue.set(state, "supportedTlds", supportedTlds);
},
setDomainData(state: UnsState, { address, domainData }: { address: string; domainData: any }): void {
Vue.set(state.domainData, address, domainData);
},
clear(state: UnsState) {
Vue.set(state, "supportedTlds", []);
Vue.set(state, "domainData", new Map());
},
});

export const actions = actionTree(
{ state, getters, mutations },
{
async lookupDomain({ commit, getters }, { address }: { address: string; ticker: string }) {
try {
const domain = address ? address.trim().toLowerCase() : "";
let supportedTlds = getters.getSupportedTld;
if (supportedTlds.length === 0) {
const response = await fetch(tldAPI);
const data = await response.json();
if (data.tlds) {
supportedTlds = data.tlds;
commit("setSupportedTlds", supportedTlds);
}
}
const isValidDomain = supportedTlds?.some((tld) => domain.endsWith(tld)) ?? false;
if (isValidDomain) {
const response = await fetch(resolutionService + domain, {
method: "get",
headers: new Headers({
Authorization: "Bearer " + process.env.UNS_KEY,
}),
});
const data = await response.json();
if (data.records) {
commit("setDomainData", { address: domain, domainData: data.records });
}
}
} catch (e) {
console.log(e);
}
},
}
);

// This compiles to nothing and only serves to return the correct type of the accessor
export const accessorType = getAccessorType({
state,
getters,
mutations,
actions,
modules: {},
});

0 comments on commit 89de2f4

Please sign in to comment.