區塊鏈錢包開發(Android篇)
本文主要內容為區塊鏈錢包移動端(Android)開發,介紹比特幣錢包和以太坊錢包的開發過程,包含錢包的主要功能: 建立錢包,錢包餘額,匯出錢包,錢包轉賬等。ofollow,noindex">Demo地址
區塊鏈錢包
在日常生活中,大家都會買個錢包用於存放政府機構發行的紙幣,那麼什麼是數字資產世界的錢包呢?
以太坊錢包:Mist 、Parity、MyEhterWallet、ImToken、MetaTask、Ledger(硬體錢包)
- 助記詞等價於私鑰
- Keystore + 密碼 等價於私鑰
EOS錢包
NEO錢包
量子錢包
-
On-chain
給一個錢包地址傳送數字貨幣, 這筆交易在全網廣播、被確認、被打包進區塊。這是發生在鏈上的,被稱為on-chain交易。on-chain錢包需要自己保管私鑰。
-
Off-chain
相對於on-chain交易是off-chain交易。通常,通過交易所進行的交易是off-chain的,本人並沒有私鑰。私鑰在交易所,由交易所託管。所以交易所的錢包也是中心化的錢包。
-
冷錢包
冷即離線、斷網,也就是說私鑰儲存的位置不能被網路所訪問。例如紙錢包、腦錢包、硬體錢包等等。
-
熱錢包
熱即聯網,也就是私鑰儲存在能被網路訪問的位置。 例如存放在交易所的、線上錢包網站、手機App錢包都屬於熱錢包。通常而言,冷錢包更加安全,熱錢包使用更加方便。
-
非確定性錢包 錢包隨機生成
-
確定性錢包(HD Wallets) 同一個種子,能夠派生一樣的金鑰對集合
-
全節點錢包
除了儲存私鑰外,全節點錢包還有儲存了所有區塊的資料,最為著名的是bitcoin-core。
-
輕錢包
它不必儲存所有區塊的資料,只儲存跟自己相關的資料。基本可以實現去中心化。
-
中心化錢包
在交易所中的錢包,以及類似 OKLink 提供的保險櫃服務。
比特幣錢包
- 比特幣錢包的組成
- 比特幣錢包地址的建立過程
- BIP32
- BIP39
- BIP43
- BIP44
- BitcoinJ建立錢包
- Bitcoin錢包收款和轉賬
比特幣錢包組成
比特幣錢包分為兩部分:錢包程式和錢包檔案
錢包檔案
儲存私鑰和轉賬記錄
Wallet containing 0.01 BTC (spendable: 0.01 BTC) in: 0 pending transactions 1 unspent transactions 0 spent transactions 0 dead transactions Last seen best block: 1384907 (2018-08-22T03:38:42Z): 0000000000000030fe01a48a7cd6b0c52909a7d019084d195ae3ebd2889c82ec Keys: Earliest creation time: 2018-08-20T07:51:29Z Seed birthday: 1534751489[2018-08-20T07:51:29Z] Key to watch:tpubD92y4mcSrbcSxANfCgiWx7h7sGquSF4ogNPcUxC2GECSwgWBMNPMo2C8nxez2ngvSS4UfaGhSunemWoqZ6aAAzLb4WLsmQxDirfFgE9tG5J addr:mq5gdvJDuDEmNKFPbgMn8pGm3pyJvkSsHvhash160:68e9c9e06890527cd0f0b59d83333502ac127bef(M/0H/0/0) >>> UNSPENT: 0.01 BTC total value (sends 0.00 BTC and receives 0.01 BTC) confidence: Seen by 7 peers (most recently: 2018-08-22T03:33:33Z). Appeared in best chain at height 1384907, depth 1. Source: NETWORK a82c35c2133bd357bfa462f82d75b28787afcdcd20c8b89cd2b78f48138d6e9f updated: 2018-08-22T03:31:53Z inPUSHDATA(71)[304402205d3e0974b4604b92e09f83950b183100bd47243c9cb548f2213a9ca26e83bdd3022018278c7ce9b65982e6c67ba9acf8e6e3898f1dad80702bb1e32c4a0b61195e0f01] PUSHDATA(33)[02f8769ecddd821cc9b75c554978b4a674df28c098e640fd0188b88bf019bc31fa] outpoint:8294b8dcf6513ab13321d4dd1642bf1c19600a313bf1ebe8511521dcd4dd0277:0 outDUP HASH160 PUSHDATA(20)[8843beff2291c5a00aa00fbd8a541f800c83b86d] EQUALVERIFY CHECKSIG 1.1899548 BTC outDUP HASH160 PUSHDATA(20)[68e9c9e06890527cd0f0b59d83333502ac127bef] EQUALVERIFY CHECKSIG 0.01 BTC prps UNKNOWN #### 複製程式碼
錢包程式
錢包程式,建立公鑰來接受satoshi,使用私鑰來使用satoshi。錢包程式可以拆分出3個獨立的模組:公鑰分發模組、簽名模組、網路模組
比特幣單位: 1比特幣(Bitcoins,BTC) 0.01位元分(Bitcent,cBTC) 0.001毫位元(Milli-Bitcoins,mBTC) 0.000001微位元(Micro-Bitcoins,μBTC或uBTC) 0.00000001聰(satoshi)(基本單位) 1 bitcoin (BTC) = 1000 millibitcoins (mBTC) = 1 million microbitcoins (uBTC) = 100 million Satoshi 複製程式碼
根據三個模組的組合,可以分為全服務錢包、只簽名錢包(離線錢包和硬體錢包)、只分發錢包。
BIP協議
BIP32
BIP32 :定義了層級確定性錢包(hierarchical deterministic wallets),是一個系統可以從單一個 seed 產生一樹狀結構儲存多組 keypairs(私鑰和公鑰)。好處是可以方便的備份、轉移到其他相容裝置(因為都只需要 seed),以及分層的許可權控制等。
作用:
-
1、備份更容易。按照比特幣的原則,儘量不要使用同一個地址,一個地址只使用一次,這樣會導致頻繁備份錢包。HD錢包只需要在建立時儲存主金鑰,通過主金鑰可以派生出所有的子金鑰。
-
2、私鑰離線更安全。主私鑰離線儲存,主公鑰線上使用,通過主公鑰可以派生出所有的子公鑰。例如:給每個商品提供一個收款地址。
-
3、利於管理,許可權控制。樹狀結構類似於公司的組織架構,可以給各個部門指定一個金鑰分支。
-
4、記賬。只使用公鑰即可記賬。
BIP39
BIP39 :將seed 用方便記憶和書寫的單字表示。一般由 12 個單字組成,稱為 mnemonic code(phrase),中文稱為助記詞或助記碼。例如: average green proud remember advance trick estate oblige trouble when cube person
BIP43
BIP43對BIP32樹結構增加了子索引標識purpose的拓展m/purpose'/*
BIP32的索引:m/0'/*
BIP44的索引:m/44'/*。
BIP44
BIP44 :基於BIP32和BIP43,賦予樹狀結構中的各層特殊的意義。讓同一個 seed 可以支援多幣種、多帳戶等。各層定義如下:
m / purpose' / coin_type' / account' / change / address_index 複製程式碼
- purporse': 固定值44', 代表是BIP44
- coin_type': 這個代表的是幣種, 可以相容很多種幣, 比如BTC是0', ETH是60', 例如:btc一般是 m/44'/0'/0'/0, eth一般是 m/44'/60'/0'/0
- account':賬號
- change': 0表示外部鏈(External Chain),使用者接收比特幣,1表示內部鏈(Internal Chain),用於接收找零
- address_index:錢包索引
錢包最佳實踐
- 使用助記詞(BIP39)
- 使用層級確定性錢包(HD Wallets)(BIP32)
- 使用多目的HD Wallets(BIP43)
- 使用多幣種,多賬號的HD Wallets (BIP44)
比特幣錢包地址建立過程
1、生成128bit~256bit作為私鑰
2、通過secp256k1橢圓曲線演算法得到私鑰對應的公鑰
3、將公鑰進行SHA-256,得到公鑰Hash
4、將3的結果進行RIMEMD-160
5、將4中結果新增1個位元組版本號
6、將5中結果進行兩次SHA-256,取前4個位元組作為checksum
7、將6中結果新增到5中結果的末尾
8、將7中結果進行Base58,結果為比特幣地址
BitcoinJ建立錢包
新增依賴:
dependencies { implementation 'org.bitcoinj:bitcoinj-core:0.14.7' implementation 'org.slf4j:slf4j-api:1.7.25' implementation 'com.squareup.okhttp3:okhttp:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' implementation 'com.google.zxing:core:3.3.3'//二維碼 } 複製程式碼
Android最大方法數的限制,60K 開啟multiDexEnabled
android { compileSdkVersion 28 defaultConfig { multiDexEnabled true } } dependencies { implementation 'com.android.support:multidex:1.0.3' } 複製程式碼
建立新錢包
File walletFile = activity.getFileStreamPath("wallet-protobuf"); //建立錢包 wallet = new Wallet(Constants.NETWORK_PARAMETERS); //建立WalletFiles,設定自動儲存Wallet WalletFiles walletFiles = wallet.autosaveToFile(walletFile, 3 * 1000, TimeUnit.MILLISECONDS, null); //立即儲存 walletFiles.saveNow(); 複製程式碼
錢包建立原始碼分析:
-
Wallet
-
KeyChainGroup
-
DeterministicKeyChain
-
DeterministicSeed
protected DeterministicKeyChain(DeterministicSeed seed, @Nullable KeyCrypter crypter) { this.seed = seed; basicKeyChain = new BasicKeyChain(crypter); if (!seed.isEncrypted()) { rootKey = HDKeyDerivation.createMasterPrivateKey(checkNotNull(seed.getSeedBytes())); rootKey.setCreationTimeSeconds(seed.getCreationTimeSeconds()); addToBasicChain(rootKey); hierarchy = new DeterministicHierarchy(rootKey); for (int i = 1; i <= getAccountPath().size(); i++) { addToBasicChain(hierarchy.get(getAccountPath().subList(0, i), false, true)); } initializeHierarchyUnencrypted(rootKey); } // Else... // We can't initialize ourselves with just an encrypted seed, so we expected deserialization code to do the // rest of the setup (loading the root key). } 複製程式碼
獲取錢包地址
Address address = wallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS); address.toString(); 複製程式碼
在獲取地址的過程中會呼叫RIMEMD-160演算法處理公鑰hash:
//Utils.java public static byte[] sha256hash160(byte[] input) { byte[] sha256 = Sha256Hash.hash(input); RIPEMD160Digest digest = new RIPEMD160Digest(); digest.update(sha256, 0, sha256.length); byte[] out = new byte[20]; digest.doFinal(out, 0); return out; } 複製程式碼
處理公鑰hash後會進行Base58演算法:
//VersionedChecksummedBytes.java public final String toBase58() { // A stringified buffer is: //1 byte version + data bytes + 4 bytes check code (a truncated hash) byte[] addressBytes = new byte[1 + bytes.length + 4]; addressBytes[0] = (byte) version; System.arraycopy(bytes, 0, addressBytes, 1, bytes.length); byte[] checksum = Sha256Hash.hashTwice(addressBytes, 0, bytes.length + 1); System.arraycopy(checksum, 0, addressBytes, bytes.length + 1, 4); return Base58.encode(addressBytes); } 複製程式碼
從檔案中載入錢包
//讀取錢包檔案 File walletFile = activity.getFileStreamPath("wallet-protobuf"); if (walletFile.exists()) { InputStream inputStream = new FileInputStream(walletFile); //反序列化 wallet= new WalletProtobufSerializer().readWallet(inputStream); //設定自動儲存 wallet.autosaveToFile(walletFile, 3 * 1000, TimeUnit.MILLISECONDS, null); //清理錢包 wallet.cleanup(); } 複製程式碼
建立地址二維碼
String s = BitcoinURI.convertToBitcoinURI(address, null, null, null); Bitmap bitmap = Qr.bitmap(s); BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap); bitmapDrawable.setFilterBitmap(false); mQrImageView.setImageDrawable(bitmapDrawable); public static Bitmap bitmap(final String content) { try { final Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>(); hints.put(EncodeHintType.MARGIN, 0); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); final BitMatrix result = QR_CODE_WRITER.encode(content, BarcodeFormat.QR_CODE, 0, 0, hints); final int width = result.getWidth(); final int height = result.getHeight(); final byte[] pixels = new byte[width * height]; for (int y = 0; y < height; y++) { final int offset = y * width; for (int x = 0; x < width; x++) { pixels[offset + x] = (byte) (result.get(x, y) ? -1 : 0); } } final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8); bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(pixels)); return bitmap; } catch (final WriterException x) { log.info("problem creating qr code", x); return null; } } 複製程式碼
Bitcoin錢包收款和轉賬
比特幣錢包餘額需要統計所有錢包地址對應的UTXO
Simplified Payment Verification (SPV):節點無需下載所有的區塊資料,而只需要載入所有區塊頭資料(block header的大小為80B),即可驗證這筆交易是否曾經被比特幣網路認證過。
布隆過濾器(Bloom Filter):過濾掉那些不包含有目標地址的交易信息,這一步能避免掉大量不相關的資料下載。
建立區塊鏈
//建立區塊鏈檔案 File blockChainFile = new File(getDir("blockstore", Context.MODE_PRIVATE), "blockchain"); //建立SPVBlockStore,管理區塊資料 blockStore = new SPVBlockStore(Constants.NETWORK_PARAMETERS, blockChainFile); //載入檢查點 final InputStream checkpointsInputStream = getAssets().open("checkpoints-testnet.txt"); CheckpointManager.checkpoint(Constants.NETWORK_PARAMETERS, checkpointsInputStream, blockStore, earliestKeyCreationTime); //建立區塊鏈物件 blockChain = new BlockChain(Constants.NETWORK_PARAMETERS, wallet, blockStore); 複製程式碼
同步區塊鏈
//新增網路許可權: <uses-permission android:name="android.permission.INTERNET"/> private void startup() { Log.d(TAG, "startup: "); peerGroup = new PeerGroup(Constants.NETWORK_PARAMETERS, blockChain); peerGroup.setDownloadTxDependencies(0); // recursive implementation causes StackOverflowError peerGroup.addWallet(wallet);//設定錢包,重要 try { PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_ACTIVITIES); peerGroup.setUserAgent(USER_AGENT, packageInfo.versionName); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } peerGroup.setMaxConnections(8); int connectTimeout = (int) (15 * DateUtils.SECOND_IN_MILLIS); peerGroup.setConnectTimeoutMillis(connectTimeout); int discoveryTimeout = (int) (10 * DateUtils.SECOND_IN_MILLIS); peerGroup.addConnectedEventListener(mPeerConnectedEventListener); peerGroup.addDisconnectedEventListener(mPeerDisconnectedEventListener); peerGroup.addDiscoveredEventListener(mPeerDiscoveredEventListener); peerGroup.setPeerDiscoveryTimeoutMillis(discoveryTimeout); //新增節點探索器,重要 peerGroup.addPeerDiscovery(new PeerDiscovery() { private final PeerDiscovery normalPeerDiscovery = MultiplexingDiscovery .forServices(Constants.NETWORK_PARAMETERS, 0); @Override public InetSocketAddress[] getPeers(final long services, final long timeoutValue, final TimeUnit timeoutUnit) throws PeerDiscoveryException { return normalPeerDiscovery.getPeers(services, timeoutValue, timeoutUnit); } @Override public void shutdown() { normalPeerDiscovery.shutdown(); } }); peerGroup.startAsync(); peerGroup.startBlockChainDownload(null); } 複製程式碼
比特幣收款
獲取測試用比特幣:testnet.manu.backend.hamburg/faucet 剛收到的幣可能需要幾分鐘後才能使用
//監聽比特幣接受事件 wallet.addCoinsReceivedEventListener(mWalletListener); //刷新餘額 Coin balance = wallet.getBalance(Wallet.BalanceType.ESTIMATED); 複製程式碼
比特幣轉賬
比特幣測試鏈轉賬查詢 建立一個Tx,對Tx進行簽名,對Tx進行P2P網路廣播
Address address = Address.fromBase58(Constants.NETWORK_PARAMETERS, to); //轉賬金額,以mBTC為單位 Coin coin = MonetaryFormat.MBTC.parse(amount); //建立請求 SendRequest sendRequest = SendRequest.to(address, coin); try { //建立Transaction Transaction transaction = wallet.sendCoinsOffline(sendRequest); //通過P2P廣播 BlockChainService.broadcastTransaction(BitcoinWalletActivity.this, transaction); } catch (InsufficientMoneyException e) { Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); e.printStackTrace(); } public static void broadcastTransaction(Context context, Transaction transaction) { Intent intent = new Intent(ACTION_BROADCAST_TRANSACTION, null, context, BlockChainService.class); intent.putExtra(ACTION_BROADCAST_TRANSACTION_HASH, transaction.getHash().getBytes()); context.startService(intent); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand: "); if (intent != null) { byte[] txHash = intent.getByteArrayExtra("tx"); if (txHash != null) { Sha256Hash sha256Hash = Sha256Hash.wrap(txHash); Transaction transaction = BitcoinWalletManager.getInstance().getWallet().getTransaction(sha256Hash); peerGroup.broadcastTransaction(transaction); Log.d(TAG, "onStartCommand: " + sha256Hash.toString()); } } return super.onStartCommand(intent, flags, startId); } 複製程式碼
以太坊錢包
以太坊錢包功能與比特幣錢包功能類似,獲取使用者餘額,管理地址和金鑰,轉賬、智慧合約呼叫。以太坊錢包一般不用在本地維護區塊鏈資料,只需要使用JSON-RPC訪問
錢包檔案
KeyStore = 私鑰 + 密碼
如果使用ImToken建立錢包,建立了助記詞,密碼用來加密錢包地址對應的子私鑰,加密的結果就是Keystore.
{ "address": "001d3f1ef827552ae1114027bd3ecf1f086ba0f9", "crypto": { "cipher": "aes-128-ctr", "ciphertext": "233a9f4d236ed0c13394b504b6da5df02587c8bf1ad8946f6f2b58f055507ece", "cipherparams": { "iv": "d10c6ec5bae81b6cb9144de81037fa15" }, "kdf": "scrypt", "kdfparams": { "dklen": 32, "n": 262144, "p": 1, "r": 8, "salt": "99d37a47c7c9429c66976f643f386a61b78b97f3246adca89abe4245d2788407" }, "mac": "594c8df1c8ee0ded8255a50caf07e8c12061fd859f4b7c76ab704b17c957e842" }, "id": "4fcb2ba4-ccdb-424f-89d5-26cce304bf9c", "version": 3 } 複製程式碼
以太坊錢包地址建立過程
1、使用Secp256k1建立公私鑰
2、通過Keccak演算法得到公鑰Hash值,進而得到長度為40的地址字串
3、一般的,會在地址字串簽名加字首"0x"
Web3j建立錢包
新增Web3j依賴
implementation 'org.web3j:core:3.3.1-android' 複製程式碼
建立新錢包
這裡不涉及BIP協議,為非確定性錢包
Wallet.createStandard() 出現OOM, Out of Memoryjuejin.im/post/5b4b07…
File walletDir = contextWrapper.getDir("eth", Context.MODE_PRIVATE); //生成金鑰對 ECKeyPair ecKeyPair = Keys.createEcKeyPair(); //WalletFile = KeyStore WalletFile wallet = Wallet.createLight(PASSWORD, ecKeyPair); String walletFileName = getWalletFileName(wallet); File destination = new File(walletDir, walletFileName); objectMapper.writeValue(destination, wallet); 複製程式碼
載入錢包檔案
File[] files = walletDir.listFiles(); wallet = objectMapper.readValue(files[0], WalletFile.class); 複製程式碼
通過助記詞建立錢包
涉及BIP協議,但沒有遵循bitcoin地址只使用一次的原則,錢包一般只使用派生出來第一個地址
可通過工具檢查派生的地址是否正確
//建立助記詞 public List<String> createMnemonics() throws MnemonicException.MnemonicLengthException { SecureRandom secureRandom = new SecureRandom(); byte[] entropy = new byte[DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS / 8]; secureRandom.nextBytes(entropy); returnMnemonicCode.INSTANCE.toMnemonic(entropy); } //m / 44' / 60' / 0' / 0 //Hardened意思就是派生加固,防止獲取到一個子私鑰之後可以派生出後面的子私鑰 //必須還有上一級的父私鑰才能派生 public static final ImmutableList<ChildNumber> BIP44_ETH_ACCOUNT_ZERO_PATH = ImmutableList.of(new ChildNumber(44, true), new ChildNumber(60, true), ChildNumber.ZERO_HARDENED, ChildNumber.ZERO); //通過助記詞生成HD錢包 public void onCreateWallet(View view) { byte[] seed = MnemonicCode.toSeed(words, ""); DeterministicKey masterPrivateKey = HDKeyDerivation.createMasterPrivateKey(seed); DeterministicHierarchy deterministicHierarchy = new DeterministicHierarchy(masterPrivateKey); // m / 44' / 60' / 0' / 0 / 0 DeterministicKey deterministicKey = deterministicHierarchy .deriveChild(BIP44_ETH_ACCOUNT_ZERO_PATH, false, true, new ChildNumber(0)); byte[] bytes = deterministicKey.getPrivKeyBytes(); ECKeyPair keyPair = ECKeyPair.create(bytes); try { WalletFile walletFile = Wallet.createLight(PASSWORD, keyPair); String address = walletFile.getAddress(); mAddress.setText("0x" + address); } catch (CipherException e) { e.printStackTrace(); } } 複製程式碼
匯出錢包
匯出KeyStore
public String exportKeyStore(WalletFile wallet) { try { return objectMapper.writeValueAsString(wallet); } catch (JsonProcessingException e) { e.printStackTrace(); } return null; } 複製程式碼
匯出私鑰
public String exportPrivateKey(WalletFile wallet) { try { ECKeyPair ecKeyPair = Wallet.decrypt(PASSWORD, wallet); BigInteger privateKey = ecKeyPair.getPrivateKey(); returnNumeric.toHexStringNoPrefixZeroPadded(privateKey, Keys.PRIVATE_KEY_LENGTH_IN_HEX); } catch (CipherException e) { e.printStackTrace(); } return null; } 複製程式碼
匯出助記詞
一般可以將助記詞加密儲存,匯出時解密。注意無法從KeyStore或者私鑰匯出助記詞。
例如:使用IMToken匯入私鑰或者KeyStore建立的錢包,沒有匯出助記詞的功能 如果是通過助記詞建立的,就會有匯出助記詞的功能
ETH交易
查詢餘額
private Web3j mWeb3j = Web3jFactory.build(new HttpService("https://ropsten.infura.io/1UoO4I/")); BigInteger balance = mWeb3j.ethGetBalance(mAddress, DefaultBlockParameterName.LATEST).send().getBalance(); BigDecimal balance = Convert.fromWei(balance.toString(), Convert.Unit.ETHER); 複製程式碼
ETH轉賬
BigInteger transactionCount = mWeb3j.ethGetTransactionCount(mAddress, DefaultBlockParameterName.LATEST).send().getTransactionCount(); BigInteger gasPrice = mWeb3j.ethGasPrice().send().getGasPrice(); BigInteger gasLimit = new BigInteger("200000"); BigDecimal value = Convert.toWei(mAmountEdit.getText().toString().trim(), Convert.Unit.ETHER); String to = mToAddressEdit.getText().toString().trim(); RawTransaction etherTransaction = RawTransaction.createEtherTransaction(transactionCount, gasPrice, gasLimit, to, value.toBigInteger()); ECKeyPair ecKeyPair = Wallet.decrypt("a12345678", mWalletFile); Credentials credentials = Credentials.create(ecKeyPair); byte[] bytes = TransactionEncoder.signMessage(etherTransaction, credentials); String hexValue = Numeric.toHexString(bytes); String transactionHash = mWeb3j.ethSendRawTransaction(hexValue).send().getTransactionHash(); 複製程式碼
Token交易
獲取某Token餘額
呼叫ERC20代幣智慧合約,獲取當前地址的餘額
//建立Function private Function balanceOf(String owner) { return new Function("balanceOf", Collections.singletonList(new Address(owner)), Collections.singletonList(new TypeReference<Uint256>(){})); } Function function = balanceOf(mAddress); //呼叫智慧合約 String s = callSmartContractFunction(function, CONTRACT_ADDRESS); List<Type> decode = FunctionReturnDecoder.decode(s, function.getOutputParameters()); if (decode != null && decode.size() > 0) { Uint256 type = (Uint256) decode.get(0); BigInteger tokenBalance = type.getValue(); } private String callSmartContractFunction( Function function, String contractAddress) throws Exception { String encodedFunction = FunctionEncoder.encode(function); org.web3j.protocol.core.methods.response.EthCall response = mWeb3j.ethCall( Transaction.createEthCallTransaction( mAddress, contractAddress, encodedFunction), DefaultBlockParameterName.LATEST) .sendAsync().get(); return response.getValue(); } 複製程式碼
Token轉賬
//建立Function private Function transfer(String to, BigInteger value) { return new Function( "transfer", Arrays.asList(new Address(to), new Uint256(value)), Collections.singletonList(new TypeReference<Bool>() {})); } Function transfer = transfer(to, new BigInteger(amount)); //獲取私鑰,進行簽名 ECKeyPair ecKeyPair = Wallet.decrypt("a12345678", mWalletFile); Credentials credentials = Credentials.create(ecKeyPair); String transactionHash = execute(credentials, transfer, CONTRACT_ADDRESS); //執行合約呼叫 private String execute( Credentials credentials, Function function, String contractAddress) throws Exception { BigInteger nonce =mWeb3j.ethGetTransactionCount(mAddress, DefaultBlockParameterName.LATEST).send().getTransactionCount(); BigInteger gasPrice = mWeb3j.ethGasPrice().send().getGasPrice(); BigInteger gasLimit = new BigInteger("200000"); String encodedFunction = FunctionEncoder.encode(function); RawTransaction rawTransaction = RawTransaction.createTransaction( nonce, gasPrice, gasLimit, contractAddress, encodedFunction); byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); String hexValue = Numeric.toHexString(signedMessage); EthSendTransaction transactionResponse = mWeb3j.ethSendRawTransaction(hexValue) .sendAsync().get(); return transactionResponse.getTransactionHash(); } 複製程式碼