栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

钱包开发经验分享:ETH篇

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

钱包开发经验分享:ETH篇

钱包开发经验分享:ETH篇

[TOC]

开发前的准备

工欲善其事,必先利其器

一路开发过来,积累了一些钱包的开发利器和网站,与大家分享一下。这些东西在这行开发过的人都知道,只是给行外打算入行的人做个参考。

  • 最好用的ETH钱包–metaMask

    下载:metaMask(谷歌插件)

    简介:这是一款以太坊浏览器插件,他可以很方便的查看或操作以太坊、erc20代币余额,也方便配合remix之类的合约IDE来部署合约,支持自定义代币,支持多种测试网络和正式网络以及自定义网络节点。总而言之,这是一款十分便利好用的钱包。

  • 最官方的区块链浏览器–etherscan

    网址:以太坊官方区块链浏览器

    简介:这是以太坊最最最官方的区块链浏览器了,对于开发者而言,它不仅仅只是查询区块交易那么简单,他还有更多有利于程序员开发的功能。它提供了众多api和小工具,它是所有测试网络的父域名,可以轻松地切换查看到所有测试网络的区块和交易,在部署合约时,它又协助你发布合约,因此对于开发者而言,这是一个不可缺少的网站。

  • 获取测试币的网站–rinkeby、ropsten

    网址:rinkeby、ropsten

    简介:以太坊有很多共享的测试网络,这篇博文介绍了各个网络的区别和其区块链浏览器,其中开发者主要使用的区块链浏览器不外乎rinkeby和ropsten,上述两个网址则是这两种测试币的水龙头网站,获取测试币的教程如下:获取rinkeby测试币、获取ropsten测试币。

  • 免费的第三方节点接入–王站

    网址:infura

    简介:对于ETH钱包开发而言,这是个不可或缺的网站,当然,可能也有其他第三方节点免费对用户开放,不过我一直用的是这个网站。这个网站的作用是,我们不用搭建ETH节点也可以正常地进行ETH的开发,我们只需要动动手指注册一个账户,创建我们的项目,就能拿到一个免费接入的ETH节点,而且他还包括了所有流行的测试网络。而我之所以称之为王站,是因为它的网站图标类似一个王字。

  • 最便捷的以太坊IDE–remix

    网址:remix

    简介:对于ETH钱包开发而言,合约开发和部署或许是必不可少的一部分,为什么我会这样说?那是因为在钱包开发中,总会需要对接各种erc20的代币,而我们虽然能够在获得ETH的测试币,但是其他的代币的测试币我们是很难获得的(或者说根本无法获得),而基于erc20协议的代币代码是通用的,所以接入代币钱包的时候,我们往往是考虑自己在测试网络部署一份erc20协议的合约,并自己铸币,以方便进行后续的开发,而结合remix和metaMask来部署合约,那就是几个步骤的事情。部署合约的流程可以参考这篇教程。

ETH钱包代码参考

真正的知识就在经验中

生成钱包地址、公私钥和助记词/通过助记词恢复钱包地址、公私钥
  • 导入依赖

    
    			org.bitcoinj
    			bitcoinj-core
    			0.14.7
    
    
    
    			org.web3j
    			core
    			4.5.5
    
    
  • 初始化web3j

    private final static Web3j web3j = Web3j.build(new HttpService("https://mainnet.infura.io/v3/你自己从infura申请的id"));
    
  • 参考代码

    	public static Map ethWalletGenerate(String mnemonic, String mnemonicPath, String passWord) {
    
    		try {
    			DeterministicSeed deterministicSeed = null;
    			List mnemonicArray = null;
    
    			if (null == mnemonic || 0 == mnemonic.length()) {
    				deterministicSeed = new DeterministicSeed(new SecureRandom(), 128, "", System.currentTimeMillis() / 1000);
    				mnemonicArray = deterministicSeed.getMnemonicCode();// 助记词
    			} else {
    				deterministicSeed = new DeterministicSeed(mnemonic, null, "", System.currentTimeMillis() / 1000);
    			}
    
    			byte[] seedBytes = deterministicSeed.getSeedBytes();// 种子
    			if (null == seedBytes) {
    				logger.error("生成钱包失败");
    				return null;
    			}
    
    			//种子对象
    			DeterministicKey deterministicKey = HDKeyDerivation.createMasterPrivateKey(seedBytes);
    
    			String[] pathArray = mnemonicPath.split("/");// 助记词路径
    			for (int i = 1; i < pathArray.length; i++) {
    				ChildNumber childNumber;
    				if (pathArray[i].endsWith("'")) {
    					int number = Integer.parseInt(pathArray[i].substring(0, pathArray[i].length() - 1));
    					childNumber = new ChildNumber(number, true);
    				} else {
    					int number = Integer.parseInt(pathArray[i]);
    					childNumber = new ChildNumber(number, false);
    				}
    				deterministicKey = HDKeyDerivation.deriveChildKey(deterministicKey, childNumber);
    			}
    
    			ECKeyPair eCKeyPair = ECKeyPair.create(deterministicKey.getPrivKeyBytes());
    			WalletFile walletFile = Wallet.createStandard(passWord, eCKeyPair);
    			if (null == mnemonic || 0 == mnemonic.length()) {
    				StringBuilder mnemonicCode = new StringBuilder();
    				for (int i = 0; i < mnemonicArray.size(); i++) {
    					mnemonicCode.append(mnemonicArray.get(i)).append(" ");
    				}
    				return new HashMap() {
    					private static final long serialVersionUID = -4960785990664709623L;
    					{
    						put("walletFile", walletFile);
    						put("eCKeyPair", eCKeyPair);
    						put("mnemonic", mnemonicCode.substring(0, mnemonicCode.length() - 1));
    					}
    				};
    			} else {
    				return new HashMap() {
    					private static final long serialVersionUID = -947886783923530545L;
    					{
    						put("walletFile", walletFile);
    						put("eCKeyPair", eCKeyPair);
    					}
    				};
    			}
    		} catch (CipherException e) {
    			return null;
    		} catch (UnreadableWalletException e) {
    			return null;
    		}
    	}
    

    其中关于助记词路径(mnemonicPath)的解释请参考这篇文章:关于钱包助记词。erc20代币的钱包地址和ETH的钱包地址是通用的,所以这套代码可以用于生成ETH钱包地址,也可以用于生成erc20钱包地址。

  • 测试代码

    	
    	@Test
    	public void testGenerateEthWallet(){
    		Map wallet = AddrUtil.ethWalletGenerate(null, ETH_MNEMONI_PATH, "123456");
    		WalletFile walletFile = (WalletFile) wallet.get("walletFile");
    		String address = walletFile.getAddress();
    		ECKeyPair eCKeyPair = (ECKeyPair) wallet.get("eCKeyPair");
    		String privateKey = eCKeyPair.getPrivateKey().toString(16);
    		String publicKey = eCKeyPair.getPublicKey().toString(16);
    		String mnemonic = (String) wallet.get("mnemonic");
    		logger.warn("address: {}, privateKey: {}, publicKey: {}, mnemonic: {}", address, privateKey, publicKey, mnemonic);
    	}
    
    	
    	@Test
    	public void testGenerateEthWalletByMnemonic(){
    		Map wallet = AddrUtil.ethWalletGenerate("clown cat senior keep problem engine degree modify ritual machine syrup company", ETH_MNEMONI_PATH, "123456");
    		WalletFile walletFile = (WalletFile) wallet.get("walletFile");
    		String address = walletFile.getAddress();
    		ECKeyPair eCKeyPair = (ECKeyPair) wallet.get("eCKeyPair");
    		String privateKey = eCKeyPair.getPrivateKey().toString(16);
    		String publicKey = eCKeyPair.getPublicKey().toString(16);
    		String mnemonic = (String) wallet.get("mnemonic");
    		logger.warn("address: {}, privateKey: {}, publicKey: {}, mnemonic: {}", address, privateKey, publicKey, mnemonic);
    	}
    

进一步,我们或许希望能够从一个唯一的密钥或者助记词去推导出交易所所有的钱包地址和密钥,可以参考这面这套代码:

  • 参考代码

        
        private static DeterministicKey generateKeyFromMnemonicAndUid(String mnemonic, int id) {
     byte[] seed = MnemonicUtils.generateSeed(mnemonic, "");
    
     DeterministicKey rootKey = HDKeyDerivation.createMasterPrivateKey(seed);
     DeterministicHierarchy hierarchy = new DeterministicHierarchy(rootKey);
    
     return hierarchy.deriveChild(BIP44_ETH_ACCOUNT_ZERO_PATH, false, true, new ChildNumber(id, false));
        }
    
        
        public static String getEthAddress(String mnemonic, int id) {
     DeterministicKey deterministicKey = generateKeyFromMnemonicAndUid(mnemonic, id);
     ECKeyPair ecKeyPair = ECKeyPair.create(deterministicKey.getPrivKey());
     return Keys.getAddress(ecKeyPair);
        }
    
        
        public static BigInteger getPrivateKey(String mnemonic, int id) {
     return generateKeyFromMnemonicAndUid(mnemonic, id).getPrivKey();
        }
    
  • 测试代码

    	
    	@Test
    	public void testGenerateEthChildWallet(){
    		String ethAddress = EthUtil.getEthAddress("clown cat senior keep problem engine degree modify ritual machine syrup company", 1);
    		BigInteger privateKey = EthUtil.getPrivateKey("clown cat senior keep problem engine degree modify ritual machine syrup company", 1);
    		logger.warn("address: {}, privateKey: {}", ethAddress, privateKey);
    	}
    
获取余额/获取代币余额
  • 参考代码

        
        public static String getEthBalance(String address) {
     EthGetBalance ethGetBlance = null;
     try {
         ethGetBlance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
     } catch (IOException e) {
         logger.error("【获取ETH余额失败】 错误信息: {}", e.getMessage());
     }
     // 格式转换 WEI(币种单位) --> ETHER
     String balance = Convert.fromWei(new BigDecimal(ethGetBlance.getBalance()), Convert.Unit.ETHER).toPlainString();
     return balance;
        }
    
  • 测试代码

    	
    	@Test
    	public void testGetETHBalance(){
    		String balance = EthUtil.getEthBalance("0x09f20ff67db2c5fabeb9a2c8dd5f6b4afab7887b");
    		logger.warn("balance: {}", balance);
    	}
    
  • 参考代码

        
        public static String getTokenBalance(String account, String coinAddress) {
     Function balanceOf = new Function("balanceOf",
      Arrays.asList(new org.web3j.abi.datatypes.Address(account)),
      Arrays.>asList(new TypeReference() {
      }));
    
     if (coinAddress == null) {
         return null;
     }
     String value = null;
     try {
         value = web3j.ethCall(Transaction.createEthCallTransaction(account, coinAddress, FunctionEncoder.encode(balanceOf)), DefaultBlockParameterName.PENDING).send().getValue();
     } catch (IOException e) {
         logger.error("【获取合约代币余额失败】 错误信息: {}", e.getMessage());
         return null;
     }
     int decimal = getTokenDecimal(coinAddress);
     BigDecimal balance = new BigDecimal(new BigInteger(value.substring(2), 16).toString(10)).divide(BigDecimal.valueOf(Math.pow(10, decimal)));
     return balance.toPlainString();
        }
    
  • 测试代码

    	
    	@Test
    	public void testGetTokenBalance(){
    		String usdtBalance = EthUtil.getTokenBalance("0x09f20ff67db2c5fabeb9a2c8dd5f6b4afab7887b", "0xdac17f958d2ee523a2206206994597c13d831ec7");
    		logger.warn("usdtBalance: {}", usdtBalance);
    	}
    

    ETH的地址分为两种,一种为普通的用户地址,另一种则是合约地址,所有代币类型的转账都是向合约地址发起转账,在输入中输入实际入账的信息(地址和数量),各种代币的合约地址可以查阅以太坊最最最官方的区块浏览器。上面参考代码中获取代币精度的代码可以继续参考下面的代码。

获取代币名称、精度和符号
  • 参考代码

        
        public static String getTokenSymbol(String contractAddress) {
     String methodName = "symbol";
     List inputParameters = new ArrayList<>();
     List> outputParameters = new ArrayList<>();
    
     TypeReference typeReference = new TypeReference() {
     };
     outputParameters.add(typeReference);
    
     Function function = new Function(methodName, inputParameters, outputParameters);
    
     String data = FunctionEncoder.encode(function);
     Transaction transaction = Transaction.createEthCallTransaction("0x0000000000000000000000000000000000000000", contractAddress, data);
    
     EthCall ethCall = null;
     try {
         ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).sendAsync().get();
     } catch (InterruptedException e) {
         logger.error("获取代币符号失败");
         e.printStackTrace();
     } catch (ExecutionException e) {
         logger.error("获取代币符号失败");
         e.printStackTrace();
     }
     List results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
     if (null == results || 0 == results.size()) {
         return "";
     }
     return results.get(0).getValue().toString();
        }
    
        
        public static String getTokenName(String contractAddr) {
     String methodName = "name";
     List inputParameters = new ArrayList<>();
     List> outputParameters = new ArrayList<>();
    
     TypeReference typeReference = new TypeReference() {
     };
     outputParameters.add(typeReference);
    
     Function function = new Function(methodName, inputParameters, outputParameters);
    
     String data = FunctionEncoder.encode(function);
     Transaction transaction = Transaction.createEthCallTransaction("0x0000000000000000000000000000000000000000", contractAddr, data);
    
     EthCall ethCall = null;
     try {
         ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).sendAsync().get();
     } catch (InterruptedException e) {
         logger.error("获取代币名称失败");
         e.printStackTrace();
     } catch (ExecutionException e) {
         logger.error("获取代币名称失败");
         e.printStackTrace();
     }
     List results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
     if (null == results || results.size() <= 0) {
         return "";
     }
     return results.get(0).getValue().toString();
        }
    
        
        public static int getTokenDecimal(String contractAddr) {
     String methodName = "decimals";
     List inputParameters = new ArrayList<>();
     List> outputParameters = new ArrayList<>();
    
     TypeReference typeReference = new TypeReference() {
     };
     outputParameters.add(typeReference);
    
     Function function = new Function(methodName, inputParameters, outputParameters);
    
     String data = FunctionEncoder.encode(function);
     Transaction transaction = Transaction.createEthCallTransaction("0x0000000000000000000000000000000000000000", contractAddr, data);
    
     EthCall ethCall = null;
     try {
         ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).sendAsync().get();
     } catch (InterruptedException e) {
         logger.error("获取代币精度失败");
         e.printStackTrace();
     } catch (ExecutionException e) {
         logger.error("获取代币精度失败");
         e.printStackTrace();
     }
     List results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
     if (null == results || 0 == results.size()) {
         return 0;
     }
     return Integer.parseInt(results.get(0).getValue().toString());
        }
    
  • 测试代码

    	
    	@Test
    	public void testGetTokenInfo(){
    		String usdtContractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7";
    		String tokenName = EthUtil.getTokenName(usdtContractAddress);
    		String tokenSymbol = EthUtil.getTokenSymbol(usdtContractAddress);
    		int tokenDecimal = EthUtil.getTokenDecimal(usdtContractAddress);
    		logger.warn("name: {}, symbol: {}, decimal: {}", tokenName, tokenSymbol, tokenDecimal);
    	}
    
获取交易
  • 参考代码

        
        public static List getTxByHeight(BigInteger height) {
     List transactions = new ArrayList<>();
     try {
         EthBlock.Block block = web3j.ethGetBlockByNumber(DefaultBlockParameter.valueOf(height), false).send().getBlock();
         for (EthBlock.TransactionResult transactionResult : block.getTransactions()) {
      Transaction transaction = web3j.ethGetTransactionByHash((String) transactionResult.get()).send().getTransaction().get();
      transactions.add(transaction);
         }
         logger.info("【获取交易数据成功】 区块哈希: {}, 区块高度: {}", block.getHash(), block.getNumber());
     } catch (IOException e) {
         logger.error("【获取交易数据失败】 错误信息: {}", e.getMessage());
         return null;
     }
     return transactions;
        }
    
    		
        public static Transaction getTxByTxid(String txid) {
     Transaction transaction = null;
     try {
         transaction = web3j.ethGetTransactionByHash(txid).send().getTransaction().orElse(null);
         logger.info("【获取交易信息成功】 {} : {}", txid, new Gson().toJson(transaction));
     } catch (IOException e) {
         logger.info("【获取交易信息失败】 交易哈希: {}, 错误信息: {}", txid, e.getMessage());
         return null;
     }
     return transaction;
        }
    
        
        public static Map getTokenTxInfo(Transaction transaction){
     Map result = new HashMap<>();
     String input = transaction.getInput();
     if(!Erc20Util.isTransferFunc(input)) {
          return null;
     }
     result.put("to", Erc20Util.getToAddress(input));
     result.put("amount", Erc20Util.getTransferValue(input).divide(BigDecimal.valueOf(Math.pow(10, getTokenDecimal(transaction.getTo())))));
     result.put("txid", transaction.getHash());
     result.put("from", transaction.getFrom());
     result.put("height", transaction.getBlockNumber());
     result.put("txFee", Convert.fromWei(transaction.getGasPrice().multiply(transaction.getGas()).toString(10), Convert.Unit.ETHER));
     result.put("gas", transaction.getGas());
     result.put("gasPrice", transaction.getGasPrice());
     return result;
        }
    
        
        public static Map getEthTxInfo(Transaction transaction){
     Map result = new HashMap<>();
     result.put("to", transaction.getTo());
     result.put("amount", Convert.fromWei(transaction.getValue().toString(10), Convert.Unit.ETHER));
     result.put("txid", transaction.getHash());
     result.put("from", transaction.getFrom());
     result.put("height", transaction.getBlockNumber());
     result.put("txFee", Convert.fromWei(transaction.getGasPrice().multiply(transaction.getGas()).toString(10), Convert.Unit.ETHER));
     result.put("gas", transaction.getGas());
     result.put("gasPrice", transaction.getGasPrice());
     return result;
        }
    
  • 测试代码

    	
    	@Test
    	public void testGetTransactionByTxid(){
    		Transaction ethTx = EthUtil.getTxByTxid("0xd05798408be19ec0adc5e0a7397b4e9d294b8e136eacc1eb606be45533eb97f1");
    		Map ethTxInfo = EthUtil.getEthTxInfo(ethTx);
    
    		Transaction usdtTx = EthUtil.getTxByTxid("0xd5443fad2feafd309f28d86d39af2e3f112b1ca1b8cdce8a2b6b9cdcdef5ad59");
    		Map usdtTxInfo = EthUtil.getTokenTxInfo(usdtTx);
    
    		logger.warn("txInfo: {}, usdtTxInfo: {}", new Gson().toJson(ethTxInfo), new Gson().toJson(usdtTxInfo));
    	}
    
    	
    	@Test
    	public void testGetTransactionByBlockHeight(){
    		List transactions = EthUtil.getTxByHeight(new BigInteger("9159698"));
    		logger.warn("txCount: {}", transactions.size());
    	}
    
ETH/代币离线签名转账
  • 参考代码

        
        public static String sendEthTx(String from, String to, BigInteger gasLimit, BigInteger gasPrice, BigDecimal amount, Credentials credentials) {
    
     try {
         BigInteger nonce = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.PENDING).send().getTransactionCount();
    
         BigInteger amountWei = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();
    
         RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, amountWei, "");
    
         byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
    
         return web3j.ethSendRawTransaction(Numeric.toHexString(signMessage)).sendAsync().get().getTransactionHash();
     }catch (Exception e) {
         logger.error("【ETH离线转账失败】 错误信息: {}", e.getMessage());
         return null;
     }
        }
    
        
        public static String sendTokenTx(String from, String to, BigInteger gasLimit, BigInteger gasPrice, BigInteger value, String coinAddress, Credentials credentials) {
    
     try {
         BigInteger nonce = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.PENDING).send().getTransactionCount();
    
         Function function = new Function(
          "transfer",
          Arrays.asList(new org.web3j.abi.datatypes.Address(to),
    new org.web3j.abi.datatypes.generated.Uint256(value)),
          Collections.>emptyList());
         String data = FunctionEncoder.encode(function);
    
         RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, coinAddress, data);
         byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
    
         return web3j.ethSendRawTransaction(Numeric.toHexString(signMessage)).sendAsync().get().getTransactionHash();
     }catch (Exception e) {
         logger.error("【代币离线转账失败】 错误信息: {}", e.getMessage());
         return null;
     }
        }
    
  • 测试代码

    	
    	@Test
    	public void testETHTransfer() throws Exception{
    		String from = "0xB7Cd09d73a1719b90469Edf7Aa1942d8f89Ba21f";
    		String to = "0xF0B8412C211261B68bc797f31F642Aa14fbDC007";
    		String privateKey = "密钥不可见";
    		BigDecimal value = Convert.toWei("1", Convert.Unit.WEI);
    		BigInteger gasPrice = EthUtil.web3j.ethGasPrice().send().getGasPrice();
    		BigInteger gasLimit = EthUtil.web3j.ethEstimateGas(new Transaction(from, null, null, null, to, value.toBigInteger(), null)).send().getAmountUsed();
    		String txid = EthUtil.sendEthTx(from, to, gasLimit, gasPrice, value, Credentials.create(privateKey));
    		logger.warn("txid: {}", txid);
    	}
    
    	
    	@Test
    	public void testTokenTransfer() throws Exception{
    		String from = "0xB7Cd09d73a1719b90469Edf7Aa1942d8f89Ba21f";
    		String to = "0xF0B8412C211261B68bc797f31F642Aa14fbDC007";
    		String contractAddress = "0x6a26797a73f558a09a47d2dd56fbe03227a31dbb";
    		String privateKey = "密钥不可见";
    		BigInteger value = BigDecimal.valueOf(Math.pow(10, EthUtil.getTokenDecimal(contractAddress))).toBigInteger();
    		BigInteger gasPrice = EthUtil.web3j.ethGasPrice().send().getGasPrice();
    		BigInteger gasLimit = EthUtil.getTransactionGasLimit(from, to, contractAddress, value);
    		String txid = EthUtil.sendTokenTx(from, to, gasLimit, gasPrice, gasLimit, contractAddress, Credentials.create(privateKey));
    		logger.warn("txid: {}", txid);
    	}
    

    代码里的合约地址是我在rinkeby测试网络发布的一个测试币:TestCoin Token(TCT),不想自己部署合约的同学可以关注我的公众号,发钱包地址给我,我会发一些测试币到你的钱包地址。在上面的代码里,比较玩味的是关于nonce值的管理,关于nonce值的解释可以参考这篇文章。在上面的代码里,nonce值我们是通过RPC接口直接获取,这样的操作是相对简单但是却耗时最长,因为调用RPC存在网络上的开销。比较成熟的处理方式是在交易信息表中维护一个nonce字段,这样做一方面是发起一笔新的交易的时候可以更快的获取nonce值,另一方面,当交易发生错误(发起了一笔金额错误的交易)的时候,可以及时进行修改,因为以太坊的设计是:你可以发起一笔与之前nonce值一样的交易,去覆盖处于pending状态的交易。

    另外,关于矿工费的计算,正常的以太坊交易是矿工费=gasPrice*gasLimit,gasPrice是每一步计算消耗的费用,gasLimit是允许接受的最大计算次数,它们的单位是WEI,关于以太坊的单位,请参考这篇文章。而当你使用这个计算公式来计算代币的手续费时就不怎么精确了,因为代币交易消耗的gas并不是百分百消耗完的,在区块链浏览器交易页面,你能看到,每一笔代币交易都有一个gas使用率,而由于每种代币输入的脚本大小不同,因而无法确定gas的使用率。目前为止,我还没找到一个方法可以去精确计算代币的手续费。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号