區塊鏈錢包開發(Android篇)

簡介

本文主要內容為區塊鏈錢包移動端(Android)開發,介紹比特幣錢包和以太坊錢包的開發過程,包含錢包的主要功能:
創建錢包,錢包余額,導出錢包,錢包轉賬等。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 提供的保險柜服務。

比特幣錢包

https://bitcoin.org/en/developer-guide#wallets

  • 比特幣錢包的組成
  • 比特幣錢包地址的創建過程
  • 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:mq5gdvJDuDEmNKFPbgMn8pGm3pyJvkSsHv  hash160: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
     in   PUSHDATA(71)[304402205d3e0974b4604b92e09f83950b183100bd47243c9cb548f2213a9ca26e83bdd3022018278c7ce9b65982e6c67ba9acf8e6e3898f1dad80702bb1e32c4a0b61195e0f01] PUSHDATA(33)[02f8769ecddd821cc9b75c554978b4a674df28c098e640fd0188b88bf019bc31fa]
          outpoint:8294b8dcf6513ab13321d4dd1642bf1c19600a313bf1ebe8511521dcd4dd0277:0
     out  DUP HASH160 PUSHDATA(20)[8843beff2291c5a00aa00fbd8a541f800c83b86d] EQUALVERIFY CHECKSIG 1.1899548 BTC
     out  DUP 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協議

Bitcoin Improvement Proposals

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

Wordlists

工具

BIP43

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創建錢包

Bitcoinj是比特幣協議Java版本實現的庫。

添加依賴:

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);
}

比特幣收款

獲取測試用比特幣:https://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);
}

以太坊錢包

https://github.com/ethereumbook/ethereumbook/blob/develop/wallets.asciidoc

以太坊錢包功能與比特幣錢包功能類似,獲取用戶余額,管理地址和密鑰,轉賬、智能合約調用。以太坊錢包一般不用在本地維護區塊鏈數據,只需要使用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

添加Web3j依賴

implementation 'org.web3j:core:3.3.1-android'

創建新錢包

這里不涉及BIP協議,為非確定性錢包

Wallet.createStandard() 出現OOM, Out of Memory
https://juejin.im/post/5b4b07f8e51d45199060fe1f

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);
    return  MnemonicCode.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();
        return  Numeric.toHexStringNoPrefixZeroPadded(privateKey, Keys.PRIVATE_KEY_LENGTH_IN_HEX);
    } catch (CipherException e) {
        e.printStackTrace();
    }
    return null;
}

導出助記詞

一般可以將助記詞加密存儲,導出時解密。注意無法從KeyStore或者私鑰導出助記詞。

例如:使用IMToken導入私鑰或者KeyStore創建的錢包,沒有導出助記詞的功能
如果是通過助記詞創建的,就會有導出助記詞的功能

ETH交易

獲取Robsten測試網絡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();
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容

  • 你看,這幾款,都是我剛剛做完評測的樣機,我覺得360這款青春版的就很適合你,還有這個三星的,這款剛出的曲屏的,還...
    子壬閱讀 454評論 0 1
  • sed是一個非交互性性文本編輯器,它編輯文件或標準輸入導出的文件拷貝。標準輸入可能是來自鍵盤、文件重定向、字符串或...
    貝問水閱讀 296評論 0 2
  • 雖然中文現在在全世界火了起來,但是沒辦法也只是火了起來,還不是通用,相信出國的很多小伙伴就有很深刻的感受。...
    大唐小洪閱讀 310評論 0 3
  • 1,從本篇文章/音頻/視頻中我學到的最重要的概念: 所有夢想在剛開始在實現夢想的過程中永遠是萬事開頭難。但,無一不...
    學霸不懂學渣的痛閱讀 136評論 5 0