Kaynağa Gözat

添加转账逻辑

wkw 3 hafta önce
ebeveyn
işleme
cca1b08d97

+ 3 - 0
src/stores/modules/walletStore.js

@@ -143,6 +143,9 @@ export const useWalletStore = defineStore("useWalletStore", {
         if (newList.length > 0) {
           // 切换到第一个钱包
           this.switchWallet(newList[0].id);
+          router.push({
+            path: '/wallet'
+          })
         } else {
           // 当前钱包被删除,且没有其他钱包,清空状态并跳转
           for (const key in this.$state) {

+ 460 - 0
src/utils/pub.json

@@ -0,0 +1,460 @@
+[
+    {
+        "inputs": [],
+        "payable": false,
+        "stateMutability": "nonpayable",
+        "type": "constructor"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "owner",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "spender",
+                "type": "address"
+            },
+            {
+                "indexed": false,
+                "internalType": "uint256",
+                "name": "value",
+                "type": "uint256"
+            }
+        ],
+        "name": "Approval",
+        "type": "event"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "previousOwner",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "newOwner",
+                "type": "address"
+            }
+        ],
+        "name": "OwnershipTransferred",
+        "type": "event"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "from",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "to",
+                "type": "address"
+            },
+            {
+                "indexed": false,
+                "internalType": "uint256",
+                "name": "value",
+                "type": "uint256"
+            }
+        ],
+        "name": "Transfer",
+        "type": "event"
+    },
+    {
+        "constant": true,
+        "inputs": [],
+        "name": "_decimals",
+        "outputs": [
+            {
+                "internalType": "uint8",
+                "name": "",
+                "type": "uint8"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": true,
+        "inputs": [],
+        "name": "_name",
+        "outputs": [
+            {
+                "internalType": "string",
+                "name": "",
+                "type": "string"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": true,
+        "inputs": [],
+        "name": "_symbol",
+        "outputs": [
+            {
+                "internalType": "string",
+                "name": "",
+                "type": "string"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": true,
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "owner",
+                "type": "address"
+            },
+            {
+                "internalType": "address",
+                "name": "spender",
+                "type": "address"
+            }
+        ],
+        "name": "allowance",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": false,
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "spender",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "amount",
+                "type": "uint256"
+            }
+        ],
+        "name": "approve",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "constant": true,
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "account",
+                "type": "address"
+            }
+        ],
+        "name": "balanceOf",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": false,
+        "inputs": [
+            {
+                "internalType": "uint256",
+                "name": "amount",
+                "type": "uint256"
+            }
+        ],
+        "name": "burn",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "constant": true,
+        "inputs": [],
+        "name": "decimals",
+        "outputs": [
+            {
+                "internalType": "uint8",
+                "name": "",
+                "type": "uint8"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": false,
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "spender",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "subtractedValue",
+                "type": "uint256"
+            }
+        ],
+        "name": "decreaseAllowance",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "constant": true,
+        "inputs": [],
+        "name": "getOwner",
+        "outputs": [
+            {
+                "internalType": "address",
+                "name": "",
+                "type": "address"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": false,
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "spender",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "addedValue",
+                "type": "uint256"
+            }
+        ],
+        "name": "increaseAllowance",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "constant": false,
+        "inputs": [
+            {
+                "internalType": "uint256",
+                "name": "amount",
+                "type": "uint256"
+            }
+        ],
+        "name": "mint",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "constant": true,
+        "inputs": [],
+        "name": "name",
+        "outputs": [
+            {
+                "internalType": "string",
+                "name": "",
+                "type": "string"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": true,
+        "inputs": [],
+        "name": "owner",
+        "outputs": [
+            {
+                "internalType": "address",
+                "name": "",
+                "type": "address"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": false,
+        "inputs": [],
+        "name": "renounceOwnership",
+        "outputs": [],
+        "payable": false,
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "constant": true,
+        "inputs": [],
+        "name": "symbol",
+        "outputs": [
+            {
+                "internalType": "string",
+                "name": "",
+                "type": "string"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": true,
+        "inputs": [],
+        "name": "totalSupply",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "constant": false,
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "recipient",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "amount",
+                "type": "uint256"
+            }
+        ],
+        "name": "transfer",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "constant": false,
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "sender",
+                "type": "address"
+            },
+            {
+                "internalType": "address",
+                "name": "recipient",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "amount",
+                "type": "uint256"
+            }
+        ],
+        "name": "transferFrom",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "payable": false,
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "constant": false,
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "newOwner",
+                "type": "address"
+            }
+        ],
+        "name": "transferOwnership",
+        "outputs": [],
+        "payable": false,
+        "stateMutability": "nonpayable",
+        "type": "function"
+    }
+]

+ 233 - 20
src/views/wallet/transferDetail/index.vue

@@ -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>