1. 程式人生 > >以太坊區塊鏈java開發:web3j

以太坊區塊鏈java開發:web3j

以太坊 java開發依賴

        <dependency>
            <groupId>org.web3j</groupId>
            <artifactId>core</artifactId>
            <version>3.5.0</version>
        </dependency>

連線到以太坊同步節點

private String gethURL = "http://localhost:8545";

    private void connectGeth() {
        // --rpcport value HTTP-RPC伺服器監聽埠(預設值:8545)
        this.web3j = Admin.build(new HttpService(gethURL));
        if(null==web3j) {
            System.out.println("connectGeth error");
        }
    }

建立賬戶,需要輸入密碼,返回賬戶地址 0x00000...

    public String addAccount(String password) {
        // walletId:$|chainId:$|account:$
        if (web3j == null)
            connectGeth();
        String account = "";
        try {

            String fileName = WalletUtils.generateNewWalletFile(password, new File(keystorePath));
            Credentials credentials = WalletUtils.loadCredentials(password, keystorePath + "/" + fileName);
            account = credentials.getAddress();

        } catch (InvalidAlgorithmParameterException e1) {
            e1.printStackTrace();
        } catch (NoSuchAlgorithmException e1) {
            e1.printStackTrace();
        } catch (NoSuchProviderException e1) {
            e1.printStackTrace();
        } catch (CipherException e1) {
            e1.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        addCredentials(account, password);
        return account;
    }

以太幣(礦幣)轉賬

    private void transfer_ETH(String _from, String password, String _to, BigInteger value, Long logId) {
        Credentials credentials = loadCredentials(_from, password);
        try {
            //獲取nonce
            EthGetTransactionCount count=web3j.ethGetTransactionCount(_from, DefaultBlockParameterName.LATEST).send();
            BigInteger nonce=count.getTransactionCount();
            //建立交易
            //BigInteger val=Convert.toWei(new BigDecimal(value), Unit.ETHER).toBigInteger();
            RawTransaction rawTransaction=RawTransaction.createEtherTransaction(nonce, GAS_PRICE, GAS_LIMIT, _to, value);
            //驗證簽名
            byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
            String hexValue=Numeric.toHexString(signMessage);
            //傳送交易
            CompletableFuture<EthSendTransaction> sendAsync = web3j.ethSendRawTransaction(hexValue).sendAsync();
            sendAsync.whenComplete((v,e)->{
                //交易回撥
                new Thread(() -> {
                    callBackService.callbackEthSendTransaction(v, logId, e);
                }).start();
            });
        }catch(Exception e) {
			
        }
    }

載入已有賬戶的憑證

keystorePath是憑證檔案儲存的資料夾地址,例如:/home/geth/keystore

這裡讀取檔案之前,先去全域性map(預載入)裡檢查是否能拿到憑證

    private Credentials loadCredentials(String address,String password){
        Credentials credentials=credentialsMap.get(address);
        if(CommonUtil.isNull(credentials)){
            //往map里加
            credentials=addCredentials(address,password);
        }
        return credentials;
    }
    private  Credentials addCredentials(String address,String password){
        Credentials credentials=null;
        try {
            File file = new File(keystorePath);
            File[] files = file.listFiles();
            if(files!=null && files.length>0){
                for (File f : files) {
                    String a=address.trim().substring(2);
                    if (f.getName().contains(a)) {
                        //取到這個檔案,並生成credentials物件
                        credentials = WalletUtils.loadCredentials(password, f);
                        break;
                    }
                }
            }
            if (!CommonUtil.isNull(credentials)){
                credentialsMap.putIfAbsent(address, credentials);
            }else {
                throw new RuntimeException("讀取憑證錯誤,請檢查錢包檔案是否存在");
            }
        } catch (IOException  | CipherException e) {
            e.printStackTrace();
        }
        return credentials;
    }

以太坊還包括合約釋出的代幣-虛擬幣

載入執行合約也是非常常見的

智慧合約由solidity語言開發,可以編譯為java檔案

可以使用VScode下載solidity外掛,將合約編譯 .sol ---> .bin .abi .json

使用codegen-3.5.0.jar 的 SolidityFunctionWrapperGenerator.class 的 mian方法

<!-- https://mvnrepository.com/artifact/org.web3j/codegen -->

<dependency>

<groupId>org.web3j</groupId>

<artifactId>codegen</artifactId>

<version>3.5.0</version>

</dependency>

並需要附帶引數

#來自https://docs.web3j.io/smart_contracts.html

$ web3j solidity generate [--javaTypes|--solidityTypes] /path/to/<smart-contract>.bin /path/to/<smart-contract>.abi -o /path/to/src/main/java -p com.your.organisation.name
private static final String USAGE = "solidity generate "
    + "[--javaTypes|--solidityTypes] "
    + "<input binary file>.bin <input abi file>.abi "
    + "-p|--package <base package name> "
    + "-o|--output <destination base directory>";

從輸出資料夾拿到編譯好的java合約程式碼

 

呼叫合約方法 載入合約需要合約地址

//GAS_PRICE可以動態獲取
private BigInteger GAS_PRICE = BigInteger.valueOf(22_000_000_000L);
//GAS_LIMIT引數固定
private final BigInteger GAS_LIMIT = BigInteger.valueOf(43_000);
/**
* USDT的轉賬方法
*/
private void transfer_USDT(String _from, String password, String _to, BigInteger value, Long logId) {
    //拿到憑證
    Credentials credentials = loadCredentials(_from, password);
    //使用憑證載入合約 需要知道合約的地址 這裡涉及到GAS 是為交易手續費 從合約呼叫方賬戶上扣除以太幣
    TetherToken contract = TetherToken.load(USDTAddress, getWeb3j(), credentials, GAS_PRICE, GAS_LIMIT);
    //呼叫合約方法
    RemoteCall<TransactionReceipt> transfer = contract.transfer(_to, value);
    CompletableFuture<TransactionReceipt> sendAsync = transfer.sendAsync();
    //非同步執行 並添加回調方法
    sendAsync.whenComplete((v,e)->{
        new Thread(() -> {
            callBackService.callbackTransactionReceipt(v, logId, e);
        }).start();
    });
}

有一部分合約並不公開原始碼

在無法編譯得到java合約程式碼的情況下,可以使用 abi+合約二進位制程式碼+合約地址 呼叫

abi包括一個合約所有方法、引數、返回值

以下做一個簡單的示例,並不支援很多引數型別

import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Int;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Uint;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.tx.Contract;

import com.alibaba.fastjson.JSON;
import okkpp.function.Function;
import okkpp.function.Param;

public class WidelyContract extends Contract {

    private List<Function> functions;

    @SuppressWarnings("deprecation")
    protected WidelyContract(String abi, String contractBinary, String contractAddress, Web3j web3j,
            Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
        super(contractBinary, contractAddress, web3j, credentials, gasPrice, gasLimit);
        this.functions = JSON.parseArray(abi, Function.class);
    }

    public static WidelyContract load(String abi, String contractBinary, String contractAddress, Web3j web3j,
            Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
        return new WidelyContract(abi, contractBinary, contractAddress, web3j, credentials, gasPrice, gasLimit);
    }

    @SuppressWarnings("rawtypes")
    public Type<?> excute(String functionName, String... params) throws IOException {
        List<Type> inputs = null;
        List<TypeReference<?>> outputs = null;
        for (Function f : functions) {
            if (functionName.equals(f.getName())) {
                inputs = getInputs(f.getInputs(), params);
                outputs = getOutputs(f.getOutputs());
            }
        }
        return executeCallSingleValueReturn(new org.web3j.abi.datatypes.Function(functionName, inputs, outputs));
    }

    @SuppressWarnings("rawtypes")
    private List<Type> getInputs(List<Param> inputs, String[] params) {
        int size = inputs.size();
        List<Type> result = new ArrayList<Type>();
        for (int i = 0; i < size; i++) {
            result.add(getParam(inputs.get(i).getType(), params[i]));
        }
        return result;
    }

    private List<TypeReference<?>> getOutputs(List<Param> outputs) {
        int size = outputs.size();
        List<TypeReference<?>> result = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            result.add(getType(outputs.get(i).getType()));
        }
        return result;
    }

    private TypeReference<?> getType(String type) {
        switch (type) {
        case "address": {
            return new TypeReference<Address>() {
            };
        }
        case "string": {
            return new TypeReference<Utf8String>() {
            };
        }
        case "bool": {
            return new TypeReference<Bool>() {
            };
        }
        }
        if (type.startsWith("uint")) {
            return new TypeReference<Uint>() {
            };
        }
        if (type.startsWith("int")) {
            return new TypeReference<Int>() {
            };
        }
        return null;
    }

    @SuppressWarnings("rawtypes")
    private Type getParam(String type, String input) {
        switch (type) {
        case "address": {
            return new Address(input);
        }
        case "string": {
            return new Utf8String(input);
        }
        case "bool": {
            return new Bool(input.equals("true"));
        }
        }
        if (type.startsWith("uint")) {
            return new Uint(new BigInteger(input));
        }
        if (type.startsWith("int")) {
            return new Int(new BigInteger(input));
        }
        return null;
    }
}

另附自定義Function類

import java.util.List;

public class Function {

    private boolean constant;
    private String name;
    private boolean payable;
    private String stateMutability;
    private String type;
    private List<Param> inputs;
    private List<Param> outputs;

    public boolean isConstant() {
        return constant;
    }

    public void setConstant(boolean constant) {
        this.constant = constant;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isPayable() {
        return payable;
    }

    public void setPayable(boolean payable) {
        this.payable = payable;
    }

    public String getStateMutability() {
        return stateMutability;
    }

    public void setStateMutability(String stateMutability) {
        this.stateMutability = stateMutability;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public List<Param> getInputs() {
        return inputs;
    }

    public void setInputs(List<Param> inputs) {
        this.inputs = inputs;
    }

    public List<Param> getOutputs() {
        return outputs;
    }

    public void setOutputs(List<Param> outputs) {
        this.outputs = outputs;
    }

    @Override
    public String toString() {
        return "Function [constant=" + constant + ", name=" + name + ", payable=" + payable + ", stateMutability="
                + stateMutability + ", type=" + type + ", inputs=" + inputs + ", outputs=" + outputs + "]";
    }
}

與Param類

public class Param {

    private String name;
    private String type;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return "Param [name=" + name + ", type=" + type + "]";
    }
}