mirror of
https://github.com/andrei0x309/clear-wallet.git
synced 2024-11-18 23:41:10 +00:00
commit
1bdc7d7b37
@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Manifest Version 1.4.5
|
||||||
|
|
||||||
|
- improved gas estimation for transactions
|
||||||
|
- fixed paste buttons with new Ionic version
|
||||||
|
- improved error display
|
||||||
|
- added personal sign view for signing messages with the private key
|
||||||
|
- changed send token view to allow sending ERC20 besides native tokens
|
||||||
|
|
||||||
## Manifest Version 1.4.4
|
## Manifest Version 1.4.4
|
||||||
|
|
||||||
- added QR scaner for easier sign in with farcaster
|
- added QR scaner for easier sign in with farcaster
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
"name": "__MSG_appName__",
|
"name": "__MSG_appName__",
|
||||||
"description": "__MSG_appDesc__",
|
"description": "__MSG_appDesc__",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"version": "1.4.4",
|
"version": "1.4.5",
|
||||||
"version_name": "1.4.4",
|
"version_name": "1.4.5",
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "assets/extension-icon/wallet_16.png",
|
"16": "assets/extension-icon/wallet_16.png",
|
||||||
"32": "assets/extension-icon/wallet_32.png",
|
"32": "assets/extension-icon/wallet_32.png",
|
||||||
|
@ -36,6 +36,10 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
path: '/farcaster-actions',
|
path: '/farcaster-actions',
|
||||||
component: () => import('@/views/FarcasterActions.vue'),
|
component: () => import('@/views/FarcasterActions.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/personal-sign',
|
||||||
|
component: () => import('@/views/PersonalSign.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/tabs/',
|
path: '/tabs/',
|
||||||
component: AppTabs,
|
component: AppTabs,
|
||||||
|
@ -19,3 +19,62 @@ export const FARCASTER_PARTIAL_KEY_ABI = [
|
|||||||
"type": "function"
|
"type": "function"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const ERC20_PARTIAL_ABI = [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"internalType": "address",
|
||||||
|
"name": "account",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "balanceOf",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"internalType": "uint256",
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"internalType": "address",
|
||||||
|
"name": "to",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"internalType": "uint256",
|
||||||
|
"name": "value",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "transfer",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"internalType": "bool",
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [],
|
||||||
|
"name": "decimals",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"internalType": "uint8",
|
||||||
|
"name": "",
|
||||||
|
"type": "uint8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
@ -9,3 +9,5 @@ export const exportFile = (fileName: string, content: string, type = 'json') =>
|
|||||||
link.click()
|
link.click()
|
||||||
document.body.removeChild(link)
|
document.body.removeChild(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
@ -281,7 +281,15 @@ export const copyText = async (address: string, toastRef: Ref<boolean>) => {
|
|||||||
export const getUrl = (url: string) => chrome.runtime.getURL(url)
|
export const getUrl = (url: string) => chrome.runtime.getURL(url)
|
||||||
|
|
||||||
export const paste = (id: string) => {
|
export const paste = (id: string) => {
|
||||||
const el = document.getElementById(id)
|
const el = document.querySelector(`#${id} input.native-input`) as HTMLInputElement
|
||||||
|
if(el){
|
||||||
|
el.focus();
|
||||||
|
(document as any)?.execCommand('paste')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pasteTextArea = (id: string) => {
|
||||||
|
const el = document.querySelector(`#${id} textarea`) as HTMLTextAreaElement
|
||||||
if(el){
|
if(el){
|
||||||
el.focus();
|
el.focus();
|
||||||
(document as any)?.execCommand('paste')
|
(document as any)?.execCommand('paste')
|
||||||
|
@ -4,6 +4,8 @@ import { mainNets } from '@/utils/networks';
|
|||||||
|
|
||||||
let provider: ethers.JsonRpcProvider | null = null
|
let provider: ethers.JsonRpcProvider | null = null
|
||||||
|
|
||||||
|
const bigIntMax = (...args: bigint[]) => args.reduce((m, e) => e > m ? e : m);
|
||||||
|
|
||||||
export const getCurrentProvider = async () => {
|
export const getCurrentProvider = async () => {
|
||||||
const network = await getSelectedNetwork()
|
const network = await getSelectedNetwork()
|
||||||
if (provider) {
|
if (provider) {
|
||||||
@ -77,7 +79,9 @@ export const getBalance = async () =>{
|
|||||||
export const getGasPrice = async () => {
|
export const getGasPrice = async () => {
|
||||||
const { provider } = await getCurrentProvider()
|
const { provider } = await getCurrentProvider()
|
||||||
const feed = await provider.getFeeData()
|
const feed = await provider.getFeeData()
|
||||||
const gasPrice = feed.maxFeePerGas ?? feed.gasPrice ?? 0n
|
const gasPrices = [ feed.gasPrice, feed.maxFeePerGas, feed.maxPriorityFeePerGas ].filter(Boolean).map((p: any) => BigInt(p))
|
||||||
|
const gasPriceFeed = bigIntMax(...gasPrices)
|
||||||
|
const gasPrice = gasPriceFeed + (gasPriceFeed / BigInt(25))
|
||||||
return {
|
return {
|
||||||
price: Number(gasPrice) / 1e9,
|
price: Number(gasPrice) / 1e9,
|
||||||
feed
|
feed
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item v-if="!isEdit">
|
<ion-item v-if="!isEdit">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
style="margin-right: 0.5rem"
|
style="margin-right: 0.5rem; cursor: pointer"
|
||||||
@click="paste('pastePk')"
|
@click="paste('pastePk')"
|
||||||
:icon="clipboardOutline"
|
:icon="clipboardOutline"
|
||||||
button
|
button
|
||||||
|
@ -13,8 +13,12 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
style="margin-right: 0.5rem"
|
style="margin-right: 0.5rem; cursor: pointer"
|
||||||
@click="paste('address')"
|
@click="
|
||||||
|
() => {
|
||||||
|
paste('address');
|
||||||
|
}
|
||||||
|
"
|
||||||
:icon="clipboardOutline"
|
:icon="clipboardOutline"
|
||||||
button
|
button
|
||||||
/>
|
/>
|
||||||
@ -131,10 +135,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
try {
|
try {
|
||||||
modalController.dismiss(null, "cancel");
|
modalController.dismiss(null, "cancel");
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -27,7 +27,11 @@
|
|||||||
></ion-input>
|
></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button>
|
<ion-item button>
|
||||||
<ion-icon :icon="clipboardOutline" @click="paste('pasteRpc')" />
|
<ion-icon
|
||||||
|
:icon="clipboardOutline"
|
||||||
|
@click="paste('pasteRpc')"
|
||||||
|
style="margin-right: 0.5rem; cursor: pointer"
|
||||||
|
/>
|
||||||
<ion-input
|
<ion-input
|
||||||
label="RPC URL(*)"
|
label="RPC URL(*)"
|
||||||
labelPlacement="stacked"
|
labelPlacement="stacked"
|
||||||
@ -46,7 +50,11 @@
|
|||||||
></ion-input>
|
></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button>
|
<ion-item button>
|
||||||
<ion-icon :icon="clipboardOutline" @click="paste('pasteExplorer')" />
|
<ion-icon
|
||||||
|
:icon="clipboardOutline"
|
||||||
|
@click="paste('pasteExplorer')"
|
||||||
|
style="margin-right: 0.5rem; cursor: pointer"
|
||||||
|
/>
|
||||||
<ion-input
|
<ion-input
|
||||||
label="Explorer"
|
label="Explorer"
|
||||||
labelPlacement="stacked"
|
labelPlacement="stacked"
|
||||||
|
@ -34,8 +34,8 @@
|
|||||||
<ion-textarea
|
<ion-textarea
|
||||||
style="overflow-y: scroll"
|
style="overflow-y: scroll"
|
||||||
aria-label="Error"
|
aria-label="Error"
|
||||||
:rows="10"
|
:rows="12"
|
||||||
:cols="20"
|
:cols="60"
|
||||||
:value="error"
|
:value="error"
|
||||||
readonly
|
readonly
|
||||||
></ion-textarea>
|
></ion-textarea>
|
||||||
|
@ -213,6 +213,12 @@ import {
|
|||||||
IonModal,
|
IonModal,
|
||||||
IonButtons,
|
IonButtons,
|
||||||
IonTextarea,
|
IonTextarea,
|
||||||
|
IonList,
|
||||||
|
IonListHeader,
|
||||||
|
IonRadioGroup,
|
||||||
|
IonLoading,
|
||||||
|
IonText,
|
||||||
|
IonRadio,
|
||||||
} from "@ionic/vue";
|
} from "@ionic/vue";
|
||||||
import { saveSelectedAccount, paste, replaceAccounts } from "@/utils/platform";
|
import { saveSelectedAccount, paste, replaceAccounts } from "@/utils/platform";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
@ -247,6 +253,12 @@ export default defineComponent({
|
|||||||
IonModal,
|
IonModal,
|
||||||
IonButtons,
|
IonButtons,
|
||||||
IonTextarea,
|
IonTextarea,
|
||||||
|
IonList,
|
||||||
|
IonListHeader,
|
||||||
|
IonRadioGroup,
|
||||||
|
IonLoading,
|
||||||
|
IonText,
|
||||||
|
IonRadio,
|
||||||
},
|
},
|
||||||
setup: () => {
|
setup: () => {
|
||||||
const name = ref("");
|
const name = ref("");
|
||||||
|
@ -146,6 +146,15 @@
|
|||||||
>
|
>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item style="margin-top: 0.3rem; margin-bottom: 0.3rem; text-align: center">
|
||||||
|
<ion-button
|
||||||
|
@click="goToPersonalSign"
|
||||||
|
expand="block"
|
||||||
|
style="margin: auto; width: 98%; font-size: 0.8rem; padding: 0.6rem"
|
||||||
|
>Personal Sign Messages</ion-button
|
||||||
|
>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<ion-loading
|
<ion-loading
|
||||||
:is-open="loading"
|
:is-open="loading"
|
||||||
cssClass="my-custom-class"
|
cssClass="my-custom-class"
|
||||||
@ -380,6 +389,10 @@ export default defineComponent({
|
|||||||
router.push("/farcaster-actions");
|
router.push("/farcaster-actions");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const goToPersonalSign = () => {
|
||||||
|
router.push("/personal-sign");
|
||||||
|
};
|
||||||
|
|
||||||
const changeSelectedAccount = async (address: string) => {
|
const changeSelectedAccount = async (address: string) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const findIndex = accounts.value.findIndex((a) => a.address == address);
|
const findIndex = accounts.value.findIndex((a) => a.address == address);
|
||||||
@ -434,6 +447,7 @@ export default defineComponent({
|
|||||||
settings,
|
settings,
|
||||||
version,
|
version,
|
||||||
goToFarcasterActions,
|
goToFarcasterActions,
|
||||||
|
goToPersonalSign,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
331
src/views/PersonalSign.vue
Normal file
331
src/views/PersonalSign.vue
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
<template>
|
||||||
|
<ion-page>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-button @click="onCancel">Back</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>Personal Sign</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<ion-item>
|
||||||
|
<ion-label style="opacity: 0.9; font-size: 0.85rem"
|
||||||
|
>Get personal signature for custom messages, useful for some websites and
|
||||||
|
developers
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<div
|
||||||
|
style="border: 1px solid var(--ion-color-medium-contrast);
|
||||||
|
margin: 0.6rem;
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Selected Account: {{ selectedAccount?.name }}</ion-label>
|
||||||
|
<ion-button
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
accountsModal = true;
|
||||||
|
toastState = false;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>Select</ion-button
|
||||||
|
>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item button>
|
||||||
|
<p style="font-size: 0.7rem; color: coral">{{ selectedAccount?.address }}</p>
|
||||||
|
</ion-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ion-item
|
||||||
|
button
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
pasteTextArea('textToSig');
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<ion-label>
|
||||||
|
<p>
|
||||||
|
Text for personal sign
|
||||||
|
<ion-icon
|
||||||
|
style="
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
position: relative;
|
||||||
|
top: 0.16rem;
|
||||||
|
"
|
||||||
|
:icon="clipboardOutline"
|
||||||
|
button
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-textarea
|
||||||
|
style="overflow-y: scroll; width: 100%"
|
||||||
|
aria-label="Enter text to sign"
|
||||||
|
id="textToSig"
|
||||||
|
:rows="4"
|
||||||
|
:cols="8"
|
||||||
|
v-model="textToSig"
|
||||||
|
></ion-textarea>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item
|
||||||
|
button
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
if (sig.length > 0) {
|
||||||
|
copyText(sig, getToastRef());
|
||||||
|
} else {
|
||||||
|
alertMsg = 'Please get signature first';
|
||||||
|
alertOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<ion-label>
|
||||||
|
<p>
|
||||||
|
Signature
|
||||||
|
<ion-icon
|
||||||
|
style="
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
position: relative;
|
||||||
|
top: 0.2rem;
|
||||||
|
"
|
||||||
|
:icon="copyOutline"
|
||||||
|
></ion-icon>
|
||||||
|
</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-textarea
|
||||||
|
style="overflow-y: scroll; width: 100%"
|
||||||
|
aria-label="Signature"
|
||||||
|
:rows="4"
|
||||||
|
:cols="8"
|
||||||
|
v-model="sig"
|
||||||
|
></ion-textarea>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-button @click="onCancel" color="light">Cancel</ion-button>
|
||||||
|
<ion-button @click="getSignature">Get Signature</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-alert
|
||||||
|
:is-open="alertOpen"
|
||||||
|
:header="alertHeader"
|
||||||
|
:message="alertMsg"
|
||||||
|
:buttons="['OK']"
|
||||||
|
@didDismiss="
|
||||||
|
() => {
|
||||||
|
alertOpen = false;
|
||||||
|
alertHeader = 'Error';
|
||||||
|
exitWallet && (window as any)?.close();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
></ion-alert>
|
||||||
|
|
||||||
|
<ion-toast
|
||||||
|
position="top"
|
||||||
|
:is-open="toastState"
|
||||||
|
@didDismiss="toastState = false"
|
||||||
|
message="Copied to clipboard"
|
||||||
|
:duration="1500"
|
||||||
|
></ion-toast>
|
||||||
|
</ion-content>
|
||||||
|
</ion-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref, Ref } from "vue";
|
||||||
|
import {
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonPage,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonInput,
|
||||||
|
IonButton,
|
||||||
|
IonAlert,
|
||||||
|
IonIcon,
|
||||||
|
onIonViewWillEnter,
|
||||||
|
IonModal,
|
||||||
|
IonButtons,
|
||||||
|
IonTextarea,
|
||||||
|
IonToast,
|
||||||
|
modalController,
|
||||||
|
} from "@ionic/vue";
|
||||||
|
import {
|
||||||
|
saveSelectedAccount,
|
||||||
|
pasteTextArea,
|
||||||
|
replaceAccounts,
|
||||||
|
unBlockLockout,
|
||||||
|
} from "@/utils/platform";
|
||||||
|
import router from "@/router";
|
||||||
|
import type { Account } from "@/extension/types";
|
||||||
|
import { triggerListner } from "@/extension/listners";
|
||||||
|
import { copyOutline } from "ionicons/icons";
|
||||||
|
import { clipboardOutline } from "ionicons/icons";
|
||||||
|
import { getAccounts, getSelectedAccount, copyText } from "@/utils/platform";
|
||||||
|
import { signMsg } from "@/utils/wallet";
|
||||||
|
import UnlockModal from "@/views/UnlockModal.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonPage,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonInput,
|
||||||
|
IonButton,
|
||||||
|
IonAlert,
|
||||||
|
IonIcon,
|
||||||
|
IonModal,
|
||||||
|
IonButtons,
|
||||||
|
IonTextarea,
|
||||||
|
IonToast,
|
||||||
|
},
|
||||||
|
setup: () => {
|
||||||
|
const textToSig = ref("");
|
||||||
|
const sig = ref("");
|
||||||
|
const alertOpen = ref(false);
|
||||||
|
const alertMsg = ref("");
|
||||||
|
const swiwModal = ref(false);
|
||||||
|
const deepLink = ref("");
|
||||||
|
const swloading = ref(false);
|
||||||
|
const warpcastLoading = ref(false);
|
||||||
|
const exitWallet = ref(false);
|
||||||
|
const alertHeader = ref("Error");
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const accounts = ref([]) as Ref<Account[]>;
|
||||||
|
const accountsModal = ref(false) as Ref<boolean>;
|
||||||
|
const selectedAccount = (ref(null) as unknown) as Ref<Account>;
|
||||||
|
const toastState = ref(false);
|
||||||
|
|
||||||
|
const getToastRef = () => toastState;
|
||||||
|
|
||||||
|
const loadData = () => {
|
||||||
|
loading.value = true;
|
||||||
|
const pAccounts = getAccounts();
|
||||||
|
const pSelectedAccount = getSelectedAccount();
|
||||||
|
Promise.all([pAccounts, pSelectedAccount]).then((res) => {
|
||||||
|
accounts.value = res[0];
|
||||||
|
selectedAccount.value = res[1];
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onIonViewWillEnter(() => {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
router.push("/tabs/home");
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeSelectedAccount = async (address: string) => {
|
||||||
|
loading.value = true;
|
||||||
|
const findIndex = accounts.value.findIndex((a) => a.address == address);
|
||||||
|
if (findIndex > -1) {
|
||||||
|
selectedAccount.value = accounts.value[findIndex];
|
||||||
|
accounts.value = accounts.value.filter((a) => a.address !== address);
|
||||||
|
accounts.value.unshift(selectedAccount.value);
|
||||||
|
const newAccounts = [...accounts.value];
|
||||||
|
await Promise.all([
|
||||||
|
saveSelectedAccount(selectedAccount.value),
|
||||||
|
replaceAccounts(newAccounts),
|
||||||
|
]);
|
||||||
|
triggerListner("accountsChanged", [newAccounts.map((a) => a.address)?.[0]]);
|
||||||
|
}
|
||||||
|
accountsModal.value = false;
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openModal = async () => {
|
||||||
|
const modal = await modalController.create({
|
||||||
|
component: UnlockModal,
|
||||||
|
componentProps: {
|
||||||
|
unlockType: "transaction",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
modal.present();
|
||||||
|
const { role } = await modal.onWillDismiss();
|
||||||
|
if (role === "confirm") return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSignature = async () => {
|
||||||
|
if (!selectedAccount.value) {
|
||||||
|
alertMsg.value = "Please select an account";
|
||||||
|
alertOpen.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!textToSig.value) {
|
||||||
|
alertMsg.value = "Please enter text to sign";
|
||||||
|
alertOpen.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((selectedAccount.value.pk ?? "").length !== 66) {
|
||||||
|
const modalResult = await openModal();
|
||||||
|
if (modalResult) {
|
||||||
|
unBlockLockout();
|
||||||
|
loading.value = true;
|
||||||
|
} else {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unBlockLockout();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const signature = await signMsg(textToSig.value);
|
||||||
|
sig.value = signature;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
alertMsg.value = "Error getting signature";
|
||||||
|
alertOpen.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
textToSig,
|
||||||
|
sig,
|
||||||
|
onCancel,
|
||||||
|
alertOpen,
|
||||||
|
alertMsg,
|
||||||
|
clipboardOutline,
|
||||||
|
pasteTextArea,
|
||||||
|
accountsModal,
|
||||||
|
changeSelectedAccount,
|
||||||
|
selectedAccount,
|
||||||
|
accounts,
|
||||||
|
copyOutline,
|
||||||
|
toastState,
|
||||||
|
deepLink,
|
||||||
|
swiwModal,
|
||||||
|
swloading,
|
||||||
|
warpcastLoading,
|
||||||
|
window,
|
||||||
|
exitWallet,
|
||||||
|
alertHeader,
|
||||||
|
getSignature,
|
||||||
|
getToastRef,
|
||||||
|
copyText,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
@ -20,7 +20,11 @@
|
|||||||
<ion-button @click="openAbiListModal()" expand="block">Load Abi</ion-button>
|
<ion-button @click="openAbiListModal()" expand="block">Load Abi</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-icon :icon="clipboardOutline" @click="paste('pasteContract')" />
|
<ion-icon
|
||||||
|
:icon="clipboardOutline"
|
||||||
|
@click="paste('pasteContract')"
|
||||||
|
style="margin-right: 0.5rem; cursor: pointer"
|
||||||
|
/>
|
||||||
<ion-input
|
<ion-input
|
||||||
label="Contract Address(*)"
|
label="Contract Address(*)"
|
||||||
label-placement="stacked"
|
label-placement="stacked"
|
||||||
|
@ -2,11 +2,26 @@
|
|||||||
<ion-page>
|
<ion-page>
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Send Native Token</ion-title>
|
<ion-title>Send Tokens</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding">
|
<ion-content class="ion-padding">
|
||||||
|
<ion-segment
|
||||||
|
style="width: auto; padding: 0.5rem; margin: 0.5rem"
|
||||||
|
:value="currentSegment"
|
||||||
|
mode="ios"
|
||||||
|
@ion-change="segmentChange"
|
||||||
|
>
|
||||||
|
<ion-segment-button value="native">
|
||||||
|
<ion-label>Native</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button value="erc20">
|
||||||
|
<ion-label>ERC20</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
</ion-segment>
|
||||||
|
|
||||||
|
<template v-if="currentSegment === 'native'">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Current Network</ion-label>
|
<ion-label>Current Network</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
@ -46,7 +61,7 @@
|
|||||||
v-model="sendTo"
|
v-model="sendTo"
|
||||||
></ion-input>
|
></ion-input>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
style="margin-right: 0.5rem"
|
style="margin-right: 0.5rem; cursor: pointer"
|
||||||
@click="paste('pasteAddress')"
|
@click="paste('pasteAddress')"
|
||||||
:icon="clipboardOutline"
|
:icon="clipboardOutline"
|
||||||
button
|
button
|
||||||
@ -75,6 +90,111 @@
|
|||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-button @click="promptTransaction">Prompt Transaction</ion-button>
|
<ion-button @click="promptTransaction">Prompt Transaction</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>ERC20 Token</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-input
|
||||||
|
aria-label="ERC20 Token"
|
||||||
|
type="text"
|
||||||
|
id="erc20"
|
||||||
|
v-model="erc20"
|
||||||
|
></ion-input>
|
||||||
|
<ion-icon
|
||||||
|
style="margin-right: 0.5rem; cursor: pointer"
|
||||||
|
@click="paste('erc20')"
|
||||||
|
:icon="clipboardOutline"
|
||||||
|
button
|
||||||
|
/>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item button>
|
||||||
|
<ion-button @click="openModalAddContact(true)">
|
||||||
|
Load address from contacts
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Send To Address:</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-input
|
||||||
|
aria-label="address"
|
||||||
|
style="font-size: 0.8rem"
|
||||||
|
id="pasteAddress"
|
||||||
|
v-model="sendTo"
|
||||||
|
></ion-input>
|
||||||
|
<ion-icon
|
||||||
|
style="margin-right: 0.5rem; cursor: pointer"
|
||||||
|
@click="paste('pasteAddress')"
|
||||||
|
:icon="clipboardOutline"
|
||||||
|
button
|
||||||
|
/>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item button>
|
||||||
|
<ion-button @click="openModalAddContact()">
|
||||||
|
Load address from contacts
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-button
|
||||||
|
@click="
|
||||||
|
async () => {
|
||||||
|
if (!erc20) {
|
||||||
|
alertOpen = true;
|
||||||
|
alertMsg = 'Invalid ERC20 address';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (loading) return;
|
||||||
|
loading = true;
|
||||||
|
await wait(100);
|
||||||
|
await balanceOfERC20();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
height="24px"
|
||||||
|
width="24px"
|
||||||
|
id="Layer_1"
|
||||||
|
style="enable-background: new 0 0 512 512"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M384,352l96-109.3h-66.1C407.1,141.8,325,64,223.2,64C117.8,64,32,150.1,32,256s85.8,192,191.2,192 c43.1,0,83.8-14.1,117.7-40.7l7.5-5.9l-43.2-46.2l-6.2,4.6c-22.1,16.3-48.3,24.9-75.8,24.9C152.6,384.7,95.1,327,95.1,256 c0-71,57.5-128.7,128.1-128.7c66.4,0,120.7,50,127.4,115.3h-74.1L384,352z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</ion-button>
|
||||||
|
<ion-label>Current Balance</ion-label>
|
||||||
|
<b v-if="currentBalanceERC20">{{ currentBalanceERC20.toFixed(8) }}</b>
|
||||||
|
<b v-else-if="currentBalanceERC20 === null">Not Fetched</b>
|
||||||
|
<b v-else-if="currentBalanceERC20 === 0">0</b>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Amount (e.g. 1.2):</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-input
|
||||||
|
aria-label="Amount (e.g. 1.2)"
|
||||||
|
type="number"
|
||||||
|
v-model="erc20Amount"
|
||||||
|
></ion-input>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-button @click="promptTransactionERC20">Prompt Transaction</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
<ion-alert
|
<ion-alert
|
||||||
:is-open="alertOpen"
|
:is-open="alertOpen"
|
||||||
@ -114,6 +234,8 @@ import {
|
|||||||
onIonViewWillEnter,
|
onIonViewWillEnter,
|
||||||
IonLoading,
|
IonLoading,
|
||||||
modalController,
|
modalController,
|
||||||
|
IonSegmentButton,
|
||||||
|
IonSegment,
|
||||||
// IonModal,
|
// IonModal,
|
||||||
// IonButtons,
|
// IonButtons,
|
||||||
// IonTextarea,
|
// IonTextarea,
|
||||||
@ -137,9 +259,18 @@ import {
|
|||||||
import { clipboardOutline } from "ionicons/icons";
|
import { clipboardOutline } from "ionicons/icons";
|
||||||
import type { Network, Account } from "@/extension/types";
|
import type { Network, Account } from "@/extension/types";
|
||||||
import { walletPromptSendTx } from "@/extension/userRequest";
|
import { walletPromptSendTx } from "@/extension/userRequest";
|
||||||
import { isAddress, formatEther, parseEther } from "ethers";
|
import {
|
||||||
import { getTxCount, getBalance } from "@/utils/wallet";
|
isAddress,
|
||||||
|
formatEther,
|
||||||
|
parseEther,
|
||||||
|
Contract,
|
||||||
|
formatUnits,
|
||||||
|
parseUnits,
|
||||||
|
} from "ethers";
|
||||||
|
import { getTxCount, getBalance, getCurrentProvider } from "@/utils/wallet";
|
||||||
import SelectedContacts from "./ContactsSelect.vue";
|
import SelectedContacts from "./ContactsSelect.vue";
|
||||||
|
import { ERC20_PARTIAL_ABI } from "@/utils/abis";
|
||||||
|
import { wait } from "@/utils/misc";
|
||||||
|
|
||||||
// import { getFromMnemonic } from "@/utils/wallet";
|
// import { getFromMnemonic } from "@/utils/wallet";
|
||||||
|
|
||||||
@ -157,6 +288,8 @@ export default defineComponent({
|
|||||||
IonAlert,
|
IonAlert,
|
||||||
IonIcon,
|
IonIcon,
|
||||||
IonLoading,
|
IonLoading,
|
||||||
|
IonSegmentButton,
|
||||||
|
IonSegment,
|
||||||
// IonModal,
|
// IonModal,
|
||||||
// IonButtons,
|
// IonButtons,
|
||||||
// IonTextarea,
|
// IonTextarea,
|
||||||
@ -171,10 +304,14 @@ export default defineComponent({
|
|||||||
const alertTitle = ref("Error");
|
const alertTitle = ref("Error");
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const amount = ref(0);
|
const amount = ref(0);
|
||||||
|
const erc20Amount = ref(0);
|
||||||
const selectedNetwork = (ref(null) as unknown) as Ref<Network>;
|
const selectedNetwork = (ref(null) as unknown) as Ref<Network>;
|
||||||
const selectedAccount = (ref(null) as unknown) as Ref<Account>;
|
const selectedAccount = (ref(null) as unknown) as Ref<Account>;
|
||||||
const currentBalance = ref(0);
|
const currentBalance = ref(0);
|
||||||
|
const currentBalanceERC20 = ref(null) as Ref<number | null>;
|
||||||
const loadingSend = ref(false);
|
const loadingSend = ref(false);
|
||||||
|
const currentSegment = ref("native");
|
||||||
|
const erc20 = ref("");
|
||||||
|
|
||||||
// let accountsProm: Promise<Account[] | undefined>;
|
// let accountsProm: Promise<Account[] | undefined>;
|
||||||
// let settingsProm: Promise<Settings | undefined>;
|
// let settingsProm: Promise<Settings | undefined>;
|
||||||
@ -197,6 +334,10 @@ export default defineComponent({
|
|||||||
// return false;
|
// return false;
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
const segmentChange = (e: CustomEvent) => {
|
||||||
|
currentSegment.value = e.detail.value;
|
||||||
|
};
|
||||||
|
|
||||||
onIonViewWillEnter(async () => {
|
onIonViewWillEnter(async () => {
|
||||||
try {
|
try {
|
||||||
selectedNetwork.value = await getSelectedNetwork();
|
selectedNetwork.value = await getSelectedNetwork();
|
||||||
@ -210,6 +351,24 @@ export default defineComponent({
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const balanceOfERC20 = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const provider = (await getCurrentProvider()).provider;
|
||||||
|
const erc20Contract = new Contract(erc20.value, ERC20_PARTIAL_ABI, provider);
|
||||||
|
const decimals = await erc20Contract.decimals();
|
||||||
|
const balance = await erc20Contract.balanceOf(selectedAccount.value.address);
|
||||||
|
currentBalanceERC20.value = Number(formatUnits(balance, decimals));
|
||||||
|
return currentBalanceERC20.value;
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
currentBalanceERC20.value = null;
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const promptTransaction = async () => {
|
const promptTransaction = async () => {
|
||||||
alertTitle.value = "Error";
|
alertTitle.value = "Error";
|
||||||
if (
|
if (
|
||||||
@ -272,7 +431,97 @@ export default defineComponent({
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const openModalAddContact = async () => {
|
const promptTransactionERC20 = async () => {
|
||||||
|
alertTitle.value = "Error";
|
||||||
|
if (
|
||||||
|
sendTo.value?.toLocaleLowerCase() ===
|
||||||
|
selectedAccount.value.address?.toLocaleLowerCase()
|
||||||
|
) {
|
||||||
|
alertOpen.value = true;
|
||||||
|
alertMsg.value = "Cannot send to self";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAddress(sendTo.value)) {
|
||||||
|
alertOpen.value = true;
|
||||||
|
alertMsg.value = "Invalid send address";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAddress(erc20.value)) {
|
||||||
|
alertOpen.value = true;
|
||||||
|
alertMsg.value = "Invalid ERC20 address";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (erc20Amount.value <= 0) {
|
||||||
|
alertOpen.value = true;
|
||||||
|
alertMsg.value = "Amount must be greater than 0";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get current erc 20 balance
|
||||||
|
try {
|
||||||
|
const balance = await balanceOfERC20();
|
||||||
|
if (balance === null) {
|
||||||
|
throw new Error("Invalid ERC20 address or balance");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alertOpen.value = true;
|
||||||
|
alertMsg.value = "Invalid ERC20 address or balance";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number(amount.value) >= Number(currentBalanceERC20.value)) {
|
||||||
|
alertOpen.value = true;
|
||||||
|
alertMsg.value = "Insufficient balance";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
let tx;
|
||||||
|
try {
|
||||||
|
const provider = (await getCurrentProvider()).provider;
|
||||||
|
const erc20Contract = new Contract(erc20.value, ERC20_PARTIAL_ABI, provider);
|
||||||
|
const decimals = await erc20Contract.decimals();
|
||||||
|
const value = parseUnits(erc20Amount.value.toString(), decimals).toString();
|
||||||
|
tx = {
|
||||||
|
from: selectedAccount.value.address,
|
||||||
|
to: erc20.value,
|
||||||
|
gasLimit: "0x0",
|
||||||
|
gasPrice: "0x0",
|
||||||
|
data: erc20Contract.interface.encodeFunctionData("transfer", [
|
||||||
|
sendTo.value,
|
||||||
|
value,
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
alertOpen.value = true;
|
||||||
|
alertMsg.value = "Error populating transaction";
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = (await walletPromptSendTx(tx)) as {
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
if (result?.error) {
|
||||||
|
alertOpen.value = true;
|
||||||
|
alertMsg.value = "Error sending transaction to chain";
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
alertTitle.value = "OK";
|
||||||
|
alertOpen.value = true;
|
||||||
|
alertMsg.value = "Transaction sent successfully";
|
||||||
|
currentBalanceERC20.value =
|
||||||
|
Number(currentBalanceERC20.value) - Number(erc20Amount.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingSend.value = false;
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openModalAddContact = async (isErc20 = false) => {
|
||||||
const modal = await modalController.create({
|
const modal = await modalController.create({
|
||||||
component: SelectedContacts,
|
component: SelectedContacts,
|
||||||
componentProps: {},
|
componentProps: {},
|
||||||
@ -282,8 +531,12 @@ export default defineComponent({
|
|||||||
|
|
||||||
const { data, role } = await modal.onWillDismiss();
|
const { data, role } = await modal.onWillDismiss();
|
||||||
if (role === "confirm") {
|
if (role === "confirm") {
|
||||||
|
if (isErc20) {
|
||||||
|
erc20.value = data.address;
|
||||||
|
} else {
|
||||||
sendTo.value = data.address;
|
sendTo.value = data.address;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -302,6 +555,14 @@ export default defineComponent({
|
|||||||
selectedAccount,
|
selectedAccount,
|
||||||
selectedNetwork,
|
selectedNetwork,
|
||||||
openModalAddContact,
|
openModalAddContact,
|
||||||
|
segmentChange,
|
||||||
|
currentSegment,
|
||||||
|
erc20,
|
||||||
|
promptTransactionERC20,
|
||||||
|
balanceOfERC20,
|
||||||
|
currentBalanceERC20,
|
||||||
|
wait,
|
||||||
|
erc20Amount,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
label="Error:"
|
label="Error:"
|
||||||
labelPlacement="stacked"
|
labelPlacement="stacked"
|
||||||
style="overflow-y: scroll"
|
style="overflow-y: scroll"
|
||||||
:rows="10"
|
:rows="18"
|
||||||
:cols="20"
|
:cols="70"
|
||||||
:value="error"
|
:value="error"
|
||||||
readonly
|
readonly
|
||||||
></ion-textarea>
|
></ion-textarea>
|
||||||
|
@ -20,7 +20,11 @@
|
|||||||
<ion-button @click="openAbiListModal()" expand="block">Load Abi</ion-button>
|
<ion-button @click="openAbiListModal()" expand="block">Load Abi</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-icon :icon="clipboardOutline" @click="paste('pasteContract')" />
|
<ion-icon
|
||||||
|
:icon="clipboardOutline"
|
||||||
|
@click="paste('pasteContract')"
|
||||||
|
style="margin-right: 0.5rem; cursor: pointer"
|
||||||
|
/>
|
||||||
<ion-input
|
<ion-input
|
||||||
label="Contract Address(*)"
|
label="Contract Address(*)"
|
||||||
label-placement="stacked"
|
label-placement="stacked"
|
||||||
|
Loading…
Reference in New Issue
Block a user