|
@@ -41,15 +41,15 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="card-box">
|
|
|
+ <!-- <div class="card-box">
|
|
|
<div class="card-title">网络费用</div>
|
|
|
<div class="card-input" style="flex-direction: column;align-items: self-start;padding: 8px 17px;">
|
|
|
<div>18.34344398867676764566000ACC</div>
|
|
|
<div class="price">STT0.01</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </div> -->
|
|
|
</div>
|
|
|
- <van-button class="footer-btn" type="primary" size="large" @click="selectPop(2)" :disabled="disabled">确认</van-button>
|
|
|
+ <van-button class="footer-btn" type="primary" size="large" @click="selectPop(2)" :disabled="isDisabled">确认</van-button>
|
|
|
<van-popup v-model:show="showWallet" position="bottom" round style="height:400px">
|
|
|
<div class="pop-content" style="height:400px">
|
|
|
<div class="pop-title">
|
|
@@ -69,25 +69,35 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="pop-detail" v-if="selectType == 2">
|
|
|
- <div class="pop-detail-title">-0.01ACC</div>
|
|
|
+ <div class="pop-detail-title">-{{gasFee}} ACC</div>
|
|
|
<div class="pop-detail-cell">
|
|
|
<div class="cell-label">付款地址:</div>
|
|
|
- <div class="cell-text">0x01Ce8d3Ae9240B029E0868f811dfF77a4F5320f2</div>
|
|
|
+ <div class="cell-text">{{walletStore.account}}</div>
|
|
|
</div>
|
|
|
<div class="pop-detail-cell">
|
|
|
<div class="cell-label">收款地址:</div>
|
|
|
- <div class="cell-text">0x01Ce8d3Ae9240B029E0868f811dfF77a4F5320f2</div>
|
|
|
+ <div class="cell-text">{{walletAddress}}</div>
|
|
|
</div>
|
|
|
<div class="pop-detail-cell">
|
|
|
<div class="cell-label">矿工费:</div>
|
|
|
- <div class="cell-text">0.000388684557647ACC</div>
|
|
|
+ <div class="cell-text">{{ gasFee }} ACC</div>
|
|
|
</div>
|
|
|
<div class="pop-btn">
|
|
|
- <van-button class="btn" type="primary" size="large" color="#4765DD" @click="showWallet = false">确认</van-button>
|
|
|
+ <van-button class="btn" type="primary" size="large" color="#4765DD" @click="confirm" :disabled="!gasFee || gasFee === '0.0000'">确认</van-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</van-popup>
|
|
|
+ <van-popup v-model:show="showPassWord" :style="{ borderRadius:'25px' }">
|
|
|
+ <div class="pop-content-password">
|
|
|
+ <div class="pop-title-password">請輸入密碼</div>
|
|
|
+ <van-field v-model="passWord" class="pop-input" type="password"/>
|
|
|
+ <div class="pop-btn-password">
|
|
|
+ <van-button type="default" class="btn-password cancel" @click="cancel">取消</van-button>
|
|
|
+ <van-button type="default" class="btn-password confirm" @click="popConfirm">確定</van-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </van-popup>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
@@ -95,18 +105,34 @@
|
|
|
import { useRouter } from 'vue-router'
|
|
|
import { useWalletStore } from "@/stores/modules/walletStore";
|
|
|
import { hotTokens } from '@/api/path/login.api'
|
|
|
+import Web3 from "web3";
|
|
|
+import pubData from "@/utils/pub.json";
|
|
|
+import { showLoadingToast, showToast } from 'vant';
|
|
|
+import { showNotify } from 'vant';
|
|
|
const router = useRouter();
|
|
|
const walletStore = useWalletStore();
|
|
|
-
|
|
|
+const web3 = new Web3(walletStore.rpcUrl);
|
|
|
|
|
|
const walletAddress = ref('')
|
|
|
const unitNum = ref('')
|
|
|
const showWallet = ref(false)
|
|
|
+const showPassWord = ref(false)
|
|
|
const selectType = ref('');
|
|
|
const hotTokensList = ref([]);
|
|
|
const selecctList = ref({})
|
|
|
-const disabled = ref(true)
|
|
|
+const passWord = ref('')
|
|
|
+const gasFee = ref('');
|
|
|
|
|
|
+const isDisabled = computed(() => {
|
|
|
+ const num = parseFloat(unitNum.value);
|
|
|
+ return (
|
|
|
+ !walletAddress.value || // 钱包地址为空
|
|
|
+ !unitNum.value || // 输入为空
|
|
|
+ isNaN(num) || num <= 0 || num >= selecctList.value.balance // 非法数值
|
|
|
+ );
|
|
|
+});
|
|
|
+
|
|
|
+// 获取代币信息
|
|
|
const gethotTokens = async () => {
|
|
|
const {data} = await hotTokens({chain: walletStore.accountName,address:walletStore.account});
|
|
|
hotTokensList.value = data;
|
|
@@ -119,11 +145,153 @@ const selectPop = (type) => {
|
|
|
showWallet.value = true;
|
|
|
selectType.value = type;
|
|
|
}
|
|
|
+watch(() => selectType.value, (val) => {
|
|
|
+ if (val === 2) {
|
|
|
+ estimateGasFee();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const estimateGasFee = async () => {
|
|
|
+ try {
|
|
|
+ const from = walletStore.account;
|
|
|
+ const to = walletAddress.value;
|
|
|
+ const amount = unitNum.value;
|
|
|
+
|
|
|
+ if (!from || !to || !amount) return;
|
|
|
+
|
|
|
+ const isACC = selecctList.value.name === 'ACC';
|
|
|
+
|
|
|
+ let tx;
|
|
|
+ if (isACC) {
|
|
|
+ // ① 直接转账
|
|
|
+ tx = {
|
|
|
+ from,
|
|
|
+ to,
|
|
|
+ value: web3.utils.toWei(amount.toString(), 'ether'),
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ // ② ERC20 代币转账
|
|
|
+ const contract = new web3.eth.Contract(pubData, selecctList.value.address);
|
|
|
+ const value = web3.utils.toWei(amount.toString(), 'ether'); // 注意小数位
|
|
|
+ tx = {
|
|
|
+ from,
|
|
|
+ to: selecctList.value.address,
|
|
|
+ data: contract.methods.transfer(to, value).encodeABI(),
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ const gasPrice = await web3.eth.getGasPrice(); // 单位 Wei
|
|
|
+ const gasLimit = await web3.eth.estimateGas(tx); // 单位 Gas
|
|
|
+
|
|
|
+ const feeWei = BigInt(gasPrice) * BigInt(gasLimit);
|
|
|
+ gasFee.value = Number(web3.utils.fromWei(feeWei.toString(), 'ether')).toFixed(8);
|
|
|
+ } catch (err) {
|
|
|
+ console.error('预估矿工费失败:', err);
|
|
|
+ gasFee.value = '0.0000';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
const changeList = (item) => {
|
|
|
selecctList.value = item;
|
|
|
showWallet.value = false;
|
|
|
unitNum.value = ''
|
|
|
}
|
|
|
+// 确认
|
|
|
+const confirm = async () => {
|
|
|
+ showWallet.value = false;
|
|
|
+ showPassWord.value = true;
|
|
|
+};
|
|
|
+// 密码取消
|
|
|
+const cancel = () => {
|
|
|
+ passWord.value = '';
|
|
|
+ showPassWord.value = false;
|
|
|
+}
|
|
|
+// 密码确认
|
|
|
+const popConfirm = () => {
|
|
|
+ if(passWord.value != walletStore.accountPassword){
|
|
|
+ showNotify({ type: 'warning', message: '请输入正确的密码' });
|
|
|
+ }else{
|
|
|
+ getData();
|
|
|
+ }
|
|
|
+}
|
|
|
+// 请求链
|
|
|
+const getData = async () => {
|
|
|
+ const loading = showLoadingToast({
|
|
|
+ message: '转账中…',
|
|
|
+ forbidClick: true,
|
|
|
+ duration: 0,
|
|
|
+ });
|
|
|
+ try {
|
|
|
+ let receipt;
|
|
|
+ if (selecctList.value.name === 'ACC') {
|
|
|
+ // ETH 转账
|
|
|
+ receipt = await sendETH(
|
|
|
+ walletStore.privateKey,
|
|
|
+ walletAddress.value,
|
|
|
+ unitNum.value
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ // ERC‑20 代币转账
|
|
|
+ receipt = await sendToken(
|
|
|
+ walletStore.privateKey,
|
|
|
+ selecctList.value.address,
|
|
|
+ walletAddress.value,
|
|
|
+ unitNum.value
|
|
|
+ );
|
|
|
+ }
|
|
|
+ showToast({ message: '转账成功', type: 'success' });
|
|
|
+ console.log('交易哈希:', receipt.transactionHash);
|
|
|
+ router.push('/wallet');
|
|
|
+ } catch (err) {
|
|
|
+ console.error('⚠️ 转账失败:', err);
|
|
|
+ const msg =
|
|
|
+ err?.message?.replace('Returned error: ', '') ||
|
|
|
+ '发生未知错误,请稍后重试';
|
|
|
+ showToast({ message: `转账失败:${msg}`, type: 'fail' });
|
|
|
+ } finally {
|
|
|
+ showWallet.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+// ========= ETH 转账 =========
|
|
|
+const sendETH = async (privateKey, toAddress, amountInEther) => {
|
|
|
+ const account = web3.eth.accounts.privateKeyToAccount(privateKey);
|
|
|
+ const from = account.address;
|
|
|
+ const nonce = await web3.eth.getTransactionCount(from, 'latest');
|
|
|
+ const gasPrice = await web3.eth.getGasPrice();
|
|
|
+ const tx = {
|
|
|
+ from,
|
|
|
+ to: toAddress,
|
|
|
+ value: web3.utils.toWei(amountInEther.toString(), 'ether'),
|
|
|
+ gas: 21000,
|
|
|
+ gasPrice,
|
|
|
+ nonce,
|
|
|
+ chainId: await web3.eth.getChainId(),
|
|
|
+ };
|
|
|
+ const signedTx = await web3.eth.accounts.signTransaction(tx, privateKey);
|
|
|
+ return await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
|
|
|
+};
|
|
|
+// ========= ERC‑20 代币转账 =========
|
|
|
+const sendToken = async (privateKey, tokenAddress, toAddress, amount) => {
|
|
|
+ const account = web3.eth.accounts.privateKeyToAccount(privateKey);
|
|
|
+ const from = account.address;
|
|
|
+ const contract = new web3.eth.Contract(pubData, tokenAddress);
|
|
|
+ const nonce = await web3.eth.getTransactionCount(from, 'latest');
|
|
|
+ const gasPrice = await web3.eth.getGasPrice();
|
|
|
+ const value = web3.utils.toWei(amount.toString(), 'ether');
|
|
|
+ const txData = contract.methods.transfer(toAddress, value).encodeABI();
|
|
|
+ const tx = {
|
|
|
+ from,
|
|
|
+ to: tokenAddress,
|
|
|
+ data: txData,
|
|
|
+ gas: 100000,
|
|
|
+ gasPrice,
|
|
|
+ nonce,
|
|
|
+ chainId: await web3.eth.getChainId(),
|
|
|
+ };
|
|
|
+ const signedTx = await web3.eth.accounts.signTransaction(tx, privateKey);
|
|
|
+ return await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
|
|
|
+};
|
|
|
+
|
|
|
onMounted(async ()=>{
|
|
|
gethotTokens();
|
|
|
})
|
|
@@ -191,6 +359,16 @@ onMounted(async ()=>{
|
|
|
color: #8D8D8D;
|
|
|
margin-top: 1px;
|
|
|
}
|
|
|
+ :deep(.van-cell) {
|
|
|
+ background:#F2F2F2 !important;
|
|
|
+ padding: 0 !important;
|
|
|
+ }
|
|
|
+ :deep(.van-field__control) {
|
|
|
+ font-family: PingFang SC, PingFang SC;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 15px;
|
|
|
+ color: #000000;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -206,16 +384,6 @@ onMounted(async ()=>{
|
|
|
box-sizing: border-box;
|
|
|
color: #FFFFFF;
|
|
|
}
|
|
|
- :deep(.van-cell) {
|
|
|
- background:#F2F2F2 !important;
|
|
|
- padding: 0 !important;
|
|
|
- }
|
|
|
- :deep(.van-field__control) {
|
|
|
- font-family: PingFang SC, PingFang SC;
|
|
|
- font-weight: 400;
|
|
|
- font-size: 15px;
|
|
|
- color: #000000;
|
|
|
- }
|
|
|
.pop-content{
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
@@ -316,4 +484,49 @@ onMounted(async ()=>{
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+.pop-content-password{
|
|
|
+ padding: 27px 35px 25px 34px;
|
|
|
+ .pop-title-password{
|
|
|
+ font-family: PingFang SC, PingFang SC;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 17px;
|
|
|
+ color: #000000;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ .pop-input{
|
|
|
+ background: #F2F2F2;
|
|
|
+ border-radius: 8px;
|
|
|
+ height: 40px;
|
|
|
+ margin: 21px 0 31px;
|
|
|
+ }
|
|
|
+ .pop-btn-password{
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ .btn-password{
|
|
|
+ width: 83px;
|
|
|
+ height: 29px;
|
|
|
+ line-height: 29px;
|
|
|
+ padding: 5px 0 !important;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-family: PingFang SC, PingFang SC;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 15px;
|
|
|
+ box-sizing:border-box;
|
|
|
+ }
|
|
|
+ .cancel{
|
|
|
+ margin-right: 17px !important;
|
|
|
+ border: 1px solid #D8D8D8;
|
|
|
+ color: #000 !important;
|
|
|
+ }
|
|
|
+ .confirm{
|
|
|
+ background: @theme-color1;
|
|
|
+ color: #FFF;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+:deep(.van-popup--center) {
|
|
|
+ margin: 0 40px !important;
|
|
|
+ width: auto !important;
|
|
|
+}
|
|
|
</style>
|