solution: support xpub addresses for bitcoin

This commit is contained in:
Igor Artamonov
2020-09-15 22:55:58 -04:00
parent 77d7ec1101
commit 96a6f22a5b
10 changed files with 764 additions and 34 deletions

View File

@@ -16,6 +16,7 @@
package io.emeraldpay.dshackle.rpc
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.protobuf.ByteString
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.Common
import io.emeraldpay.dshackle.Global
@@ -27,8 +28,12 @@ import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.MultistreamHolder
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinReader
import io.emeraldpay.dshackle.upstream.bitcoin.XpubAddresses
import io.emeraldpay.dshackle.upstream.bitcoin.data.SimpleUnspent
import io.emeraldpay.grpc.Chain
import org.bitcoinj.core.Address
import org.bitcoinj.params.MainNetParams
import org.bitcoinj.params.TestNet3Params
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.publisher.TopicProcessor
@@ -111,7 +116,7 @@ class TrackBitcoinAddressSpec extends Specification {
)
.build()
when:
def act = track.allAddresses(req)
def act = track.allAddresses(Stub(BitcoinMultistream), req).collectList().block()
then:
act == ["16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk"]
}
@@ -132,20 +137,58 @@ class TrackBitcoinAddressSpec extends Specification {
)
.build()
when:
def act = track.allAddresses(req)
def act = track.allAddresses(Stub(BitcoinMultistream), req).collectList().block()
then:
act == ["16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk", "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", "3BMqADKWoWHPASsUdHvnUL6E1jpZkMnLZz", "bc1qdthqvt6cllzej7uhdddrltdfsmnt7d0gl5ue5n"]
}
def "Null for no address provided"() {
def "Empty for no address provided"() {
setup:
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
def req = BlockchainOuterClass.BalanceRequest.newBuilder()
.build()
when:
def act = track.allAddresses(req)
def act = track.allAddresses(Stub(BitcoinMultistream), req).collectList().block()
then:
act == null
act == []
}
def "Use active for xpub"() {
setup:
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
def req = BlockchainOuterClass.BalanceRequest.newBuilder()
.setAddress(
Common.AnyAddress.newBuilder()
.setAddressXpub(
// seed: chimney battle code relief era plug finish video patch dream pumpkin govern destroy fresh color
Common.XpubAddress.newBuilder()
.setXpub(ByteString.copyFromUtf8("zpub6tz4F49K5B4m7r7EyBKYM9K44eGECaQ2AfrCybq1w7ALFatz9856vrXxAPSrteDA4d5sjUPW3ACNq8wB2V3ugXVJxvAPAYPAYHsVm3VAncL"))
.setLimit(25)
)
)
.build()
def xpubAddresses = Mock(XpubAddresses) {
1 * activeAddresses(
"zpub6tz4F49K5B4m7r7EyBKYM9K44eGECaQ2AfrCybq1w7ALFatz9856vrXxAPSrteDA4d5sjUPW3ACNq8wB2V3ugXVJxvAPAYPAYHsVm3VAncL",
0,
25
) >> Flux.fromIterable([
"bc1q25590fu8djhw9lvxxqz8ufjyfwup9h54u8fl6t",
"bc1q3k6e6vawd5l5syu9nlxn2xsch9afgunl8dnz94",
"bc1qu7hd6wycy686kakfps9c093szufjpwnh6rjs9s"
]).map { Address.fromString(MainNetParams.get(), it) }
}
def multistream = Mock(BitcoinMultistream) {
1 * getXpubAddresses() >> xpubAddresses
}
when:
def act = track.allAddresses(multistream, req).collectList().block()
then:
act == [
"bc1q25590fu8djhw9lvxxqz8ufjyfwup9h54u8fl6t",
"bc1q3k6e6vawd5l5syu9nlxn2xsch9afgunl8dnz94",
"bc1qu7hd6wycy686kakfps9c093szufjpwnh6rjs9s"
]
}
def "Build proto for common balance"() {

View File

@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.upstream.bitcoin
import io.emeraldpay.dshackle.upstream.bitcoin.data.EsploraUnspent
import org.bitcoinj.core.Address
import org.bitcoinj.params.MainNetParams
import org.bitcoinj.params.TestNet3Params
import org.mockserver.integration.ClientAndServer
import org.mockserver.model.HttpRequest
import org.mockserver.model.HttpResponse
@@ -80,4 +81,54 @@ class EsploraClientSpec extends Specification {
actTotal == 309841L
}
def "get txs"() {
setup:
def responseJson = this.class.getClassLoader().getResourceAsStream("bitcoin/esplora-txs-1.json").text
mockServer.when(
HttpRequest.request()
.withMethod("GET")
.withPath("/address/tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx/txs")
).respond(
HttpResponse.response(responseJson)
)
def client = new EsploraClient(new URI("http://localhost:23001"), null, null)
when:
def act = client.getTransactions(Address.fromString(TestNet3Params.get(), "tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx"))
then:
StepVerifier.create(act)
.expectNextMatches { list ->
def ok = list.size() == 6
if (!ok) println("invalid size")
ok = ok && list[0].txid == "a738f4bd63f785d58acd2e83e8c8c5e84e68dabacac3a1142ee47cb188103aab"
ok = ok && list[1].txid == "4a7066adb2cd8e37f1578d6991f14c12e677e8bc0556992a73a91a26d263f89a"
ok = ok && list[2].txid == "16aa2d98e37e50c4c007a815a3cb8c20026a3df467781a7e97206a730cf4ef01"
ok = ok && list[3].txid == "caaea0ca92343a2d7115c44a0f58cb6574b9349905799854e1b5a6c3b3f33587"
ok = ok && list[4].txid == "a386377a406465423275f51d6dc71a2c245acc55c356a7e851a03b508827bc1e"
ok = ok && list[5].txid == "7944cfcd3d04c58a81aaa7067616f8ac368581a847e03447d5d2917cc75b67d2"
ok
}
.expectComplete()
.verify(Duration.ofSeconds(3))
}
def "get txs when empty"() {
setup:
mockServer.when(
HttpRequest.request()
.withMethod("GET")
.withPath("/address/tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx/txs")
).respond(
HttpResponse.response("[]")
)
def client = new EsploraClient(new URI("http://localhost:23001"), null, null)
when:
def act = client.getTransactions(Address.fromString(TestNet3Params.get(), "tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx"))
then:
StepVerifier.create(act)
.expectNext([])
.expectComplete()
.verify(Duration.ofSeconds(3))
}
}

View File

@@ -0,0 +1,175 @@
/**
* Copyright (c) 2020 EmeraldPay, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.upstream.bitcoin
import org.bitcoinj.core.Address
import org.bitcoinj.params.MainNetParams
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import spock.lang.Specification
import java.time.Duration
class XpubAddressesSpec extends Specification {
def "list all for mainnet"() {
setup:
XpubAddresses addresses = new XpubAddresses(Stub(AddressActiveCheck))
when:
// seed sustain pyramid victory drum primary silver safe wrestle section leg caught praise spring prepare input
// account 0
def act = addresses.allAddresses(
"zpub6tsHyQGj1UmDcw9UavB31HSChH9rqMi8Qje5KPbnczzhFSZykmm3afivpVytxWubaabDih6AbGGhioiTCg5PssYXuVGiZ1vTVaMYvvQvvmz",
0,
4
).collectList().block()
then:
act.size() == 4
act[0].toString() == "bc1qd6p79j20w2zy5zagptf5ksjkdmhx4d5sykrz0e"
act[1].toString() == "bc1q54um5vzdjuwt6qvzvk2y5em72jedpktrvr24a3"
act[2].toString() == "bc1q3yl03ugy6l5k3te5x340m70hyeqjzmmvksnuc8"
act[3].toString() == "bc1qp0cgpkxl52r82ev9k2wpdzzk4m4nflwurj5yr8"
}
def "list all for testnet"() {
setup:
XpubAddresses addresses = new XpubAddresses(Stub(AddressActiveCheck))
when:
// seed sustain pyramid victory drum primary silver safe wrestle section leg caught praise spring prepare input
// account 1
def act = addresses.allAddresses(
"vpub5aEsY5bNGvjkHPjdMiz8FbdoiT81kmF3JwuJ1LRdoFb7DxNh1qAHQCmMeDnr6RG2EcCwzGohem3oESxZGa6YWLPW79ryCyMrdYj54uUzNNq",
0,
4
).collectList().block()
then:
act.size() == 4
act[0].toString() == "tb1q2phmcxl4tflgcrnm00jvrkh4a8n876vkjcv97m"
act[1].toString() == "tb1qnhn0psv0uqtmg64tm258kqu0thurjvutcc80xs"
act[2].toString() == "tb1qg62jwv77ul329pe4wcchr83zz02egwfh6syqsr"
act[3].toString() == "tb1q8yg5qknrl5wmc98lw8dyfgrzamzaq8c9l0g44d"
}
def "list starting from some"() {
setup:
XpubAddresses addresses = new XpubAddresses(Stub(AddressActiveCheck))
when:
// seed sustain pyramid victory drum primary silver safe wrestle section leg caught praise spring prepare input
// account 2
def act = addresses.allAddresses(
"zpub6tyKtxsQL16XpzxwFJSrVTxpKF4z8GL2WTqvdErC2iQBSEAzLqJNc8h6hg4EHMSPEavFDneR3JQjWjbZL7vtQrXMwN9bD4c9K9s1Z55aZfB",
10,
4
).collectList().block()
then:
act.size() == 4
act[0].toString() == "bc1q3dzm92mnqvkyadwl3x5cqdvxaqa5h9c37qdlgs"
act[1].toString() == "bc1q3kqug4cx95a02yhwn6geelftmw3zklrgmhjll8"
act[2].toString() == "bc1q6znmcw8zzjv3asdylgkmt0k0xvhzl9q5awh0sq"
act[3].toString() == "bc1q3fzlxvw803r8j44culrt7jz7q5y7zakushujmq"
}
def "list when only fist is active"() {
setup:
AddressActiveCheck check = Mock(AddressActiveCheck) {
1 * isActive(Address.fromString(MainNetParams.get(), "bc1qaexx257l7sgm62szw2ulj6n2v99t5ph9ekkul3")) >> Mono.just(true)
20 * isActive(_) >> Mono.just(false)
}
XpubAddresses addresses = new XpubAddresses(check)
// seed sustain pyramid victory drum primary silver safe wrestle section leg caught praise spring prepare input
// account 2
when:
def act = addresses.activeAddresses(
"zpub6tyKtxsQL16XpzxwFJSrVTxpKF4z8GL2WTqvdErC2iQBSEAzLqJNc8h6hg4EHMSPEavFDneR3JQjWjbZL7vtQrXMwN9bD4c9K9s1Z55aZfB",
0,
50
).map { it.toString() }
then:
StepVerifier.create(act)
.expectNext("bc1qaexx257l7sgm62szw2ulj6n2v99t5ph9ekkul3")
.expectComplete()
.verify(Duration.ofSeconds(1))
}
def "list when only 3rd is active"() {
setup:
AddressActiveCheck check = Mock(AddressActiveCheck) {
1 * isActive(Address.fromString(MainNetParams.get(), "bc1qah84zz7aavf3f5eyx5f29809y6xugqyphq0wtz")) >> Mono.just(true)
// 2 times before, 20 times after
22 * isActive(_) >> Mono.just(false)
}
XpubAddresses addresses = new XpubAddresses(check)
// seed sustain pyramid victory drum primary silver safe wrestle section leg caught praise spring prepare input
// account 2
when:
def act = addresses.activeAddresses(
"zpub6tyKtxsQL16XpzxwFJSrVTxpKF4z8GL2WTqvdErC2iQBSEAzLqJNc8h6hg4EHMSPEavFDneR3JQjWjbZL7vtQrXMwN9bD4c9K9s1Z55aZfB",
0,
50
).map { it.toString() }
then:
StepVerifier.create(act)
.expectNext("bc1qah84zz7aavf3f5eyx5f29809y6xugqyphq0wtz")
.expectComplete()
.verify(Duration.ofSeconds(1))
}
def "list when only 22 is active"() {
setup:
AddressActiveCheck check = Mock(AddressActiveCheck) {
// 2
1 * isActive(Address.fromString(MainNetParams.get(), "bc1qah84zz7aavf3f5eyx5f29809y6xugqyphq0wtz")) >> Mono.just(true)
// 11
1 * isActive(Address.fromString(MainNetParams.get(), "bc1q3kqug4cx95a02yhwn6geelftmw3zklrgmhjll8")) >> Mono.just(true)
// 22
1 * isActive(Address.fromString(MainNetParams.get(), "bc1qf7m2rrmrksj34vhgxmm04y43dlj7c5f58f8ku6")) >> Mono.just(true)
// 0..1 = 2
// + 3..10 = 8
// + 12..21 = 10
// + 20
40 * isActive(_) >> Mono.just(false)
}
XpubAddresses addresses = new XpubAddresses(check)
// seed sustain pyramid victory drum primary silver safe wrestle section leg caught praise spring prepare input
// account 2
when:
def act = addresses.activeAddresses(
"zpub6tyKtxsQL16XpzxwFJSrVTxpKF4z8GL2WTqvdErC2iQBSEAzLqJNc8h6hg4EHMSPEavFDneR3JQjWjbZL7vtQrXMwN9bD4c9K9s1Z55aZfB",
0,
100
).map { it.toString() }
then:
StepVerifier.create(act)
.expectNext("bc1qah84zz7aavf3f5eyx5f29809y6xugqyphq0wtz")
.expectNext("bc1q3kqug4cx95a02yhwn6geelftmw3zklrgmhjll8")
.expectNext("bc1qf7m2rrmrksj34vhgxmm04y43dlj7c5f58f8ku6")
.expectComplete()
.verify(Duration.ofSeconds(1))
}
}

View File

@@ -0,0 +1,287 @@
[
{
"txid": "a738f4bd63f785d58acd2e83e8c8c5e84e68dabacac3a1142ee47cb188103aab",
"version": 1,
"locktime": 0,
"vin": [
{
"txid": "16aa2d98e37e50c4c007a815a3cb8c20026a3df467781a7e97206a730cf4ef01",
"vout": 1,
"prevout": {
"scriptpubkey": "00142757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 2757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx",
"value": 120000
},
"scriptsig": "",
"scriptsig_asm": "",
"witness": [
"304502210084f12093ae826635c6bce4915e927dd7a2285b37d0b51148c6d6177ce72e459d02201bf0b3d7cde090d2dbc4766b7a8b3d947e1bdb02dcf03ce894ff1a105278eb8901",
"03a3aa29d96671671b065b35de511c03ea5592eafb5de7a07542633af2d42f49ea"
],
"is_coinbase": false,
"sequence": 4294967293
}
],
"vout": [
{
"scriptpubkey": "00142757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 2757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx",
"value": 119568
}
],
"size": 192,
"weight": 438,
"fee": 432,
"status": {
"confirmed": true,
"block_height": 1831865,
"block_hash": "000000000000014e643687acc0a975f9aa7319f1e9bb428e0f1d618ed6aba370",
"block_time": 1599447368
}
},
{
"txid": "4a7066adb2cd8e37f1578d6991f14c12e677e8bc0556992a73a91a26d263f89a",
"version": 1,
"locktime": 0,
"vin": [
{
"txid": "a386377a406465423275f51d6dc71a2c245acc55c356a7e851a03b508827bc1e",
"vout": 1,
"prevout": {
"scriptpubkey": "00142757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 2757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx",
"value": 120000
},
"scriptsig": "",
"scriptsig_asm": "",
"witness": [
"3045022100c5af6d33efc9a564c0e955160e0b8def089cc6c933ddf08967eac25d4ddfc09a02205ed59dcc068251dee669ca19425c0c83208d1b1c1f5b7147f4f0b7cb2d15a87201",
"03a3aa29d96671671b065b35de511c03ea5592eafb5de7a07542633af2d42f49ea"
],
"is_coinbase": false,
"sequence": 4294967295
}
],
"vout": [
{
"scriptpubkey": "00142757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 2757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx",
"value": 119568
}
],
"size": 192,
"weight": 438,
"fee": 432,
"status": {
"confirmed": true,
"block_height": 1831846,
"block_hash": "0000000000000104a5b1241385c2cfb728128ce692ab8bd8970a0372761f8887",
"block_time": 1599439188
}
},
{
"txid": "16aa2d98e37e50c4c007a815a3cb8c20026a3df467781a7e97206a730cf4ef01",
"version": 2,
"locktime": 1831845,
"vin": [
{
"txid": "a386377a406465423275f51d6dc71a2c245acc55c356a7e851a03b508827bc1e",
"vout": 0,
"prevout": {
"scriptpubkey": "00140fc773ca476039acb48162e26190b3b93cda6762",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 0fc773ca476039acb48162e26190b3b93cda6762",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qplrh8jj8vqu6edypvt3xry9nhy7d5emzyj3a3h",
"value": 1619316
},
"scriptsig": "",
"scriptsig_asm": "",
"witness": [
"3044022067011a2a7b6823146ff8291eed673e2e52703e07b8b99cf78b0fbb968e5ca980022018244b0584f4851ec9d09296007898c00f05afdcc6c2d8eafb48e95923be609801",
"031117740bfada16dbe932219d5d5ddbb0a829d83ccbfe5eb7734d8646f9a63479"
],
"is_coinbase": false,
"sequence": 4294967293
}
],
"vout": [
{
"scriptpubkey": "0014a871aa972c4ef9255a610b3b849598991ba9df1c",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 a871aa972c4ef9255a610b3b849598991ba9df1c",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1q4pc649evfmuj2knppvacf9vcnyd6nhculm5lsk",
"value": 1499175
},
{
"scriptpubkey": "00142757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 2757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx",
"value": 120000
}
],
"size": 222,
"weight": 561,
"fee": 141,
"status": {
"confirmed": true,
"block_height": 1831846,
"block_hash": "0000000000000104a5b1241385c2cfb728128ce692ab8bd8970a0372761f8887",
"block_time": 1599439188
}
},
{
"txid": "caaea0ca92343a2d7115c44a0f58cb6574b9349905799854e1b5a6c3b3f33587",
"version": 1,
"locktime": 0,
"vin": [
{
"txid": "7944cfcd3d04c58a81aaa7067616f8ac368581a847e03447d5d2917cc75b67d2",
"vout": 1,
"prevout": {
"scriptpubkey": "00142757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 2757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx",
"value": 120000
},
"scriptsig": "",
"scriptsig_asm": "",
"witness": [
"304402200fe43b74f1326e51d5c718b5c0a9319c0241de9329e7d39e613218f66bf72f1202205918d5b29b7d6887a4f61fe3f7c330beb306e195b49e7149f5de1554f8aaa10501",
"03a3aa29d96671671b065b35de511c03ea5592eafb5de7a07542633af2d42f49ea"
],
"is_coinbase": false,
"sequence": 4294967294
}
],
"vout": [
{
"scriptpubkey": "00146d2112c30c7ef1bf66a4ff2e1bfa0304c3b8f118",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 6d2112c30c7ef1bf66a4ff2e1bfa0304c3b8f118",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qd5s39scv0mcm7e4yluhph7srqnpm3ugcx2mc5d",
"value": 119568
}
],
"size": 191,
"weight": 437,
"fee": 432,
"status": {
"confirmed": true,
"block_height": 1831820,
"block_hash": "0000000009f11ea4cd3a3393d1cc355a63ea37a5a2cbf983e34cbb7f37590eb2",
"block_time": 1599426488
}
},
{
"txid": "a386377a406465423275f51d6dc71a2c245acc55c356a7e851a03b508827bc1e",
"version": 2,
"locktime": 1831819,
"vin": [
{
"txid": "7944cfcd3d04c58a81aaa7067616f8ac368581a847e03447d5d2917cc75b67d2",
"vout": 0,
"prevout": {
"scriptpubkey": "0014da239eb3f9727ccbfe73823e427fb7289e7c192a",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 da239eb3f9727ccbfe73823e427fb7289e7c192a",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qmg3eavlewf7vhlnnsglyylah9z08cxf2a0g2nv",
"value": 1739457
},
"scriptsig": "",
"scriptsig_asm": "",
"witness": [
"3044022039020c3e01db872751e8c55f584aea2820b4e7c04c5a859ac785983aa2aaf612022012e8b0f6b052cecbcbe452dc0c8b6093f0520b355d206476c934ee879e892cfe01",
"023a49d61f3162fbba038809868a3c943de0549bae2f197d78ea8670452595cece"
],
"is_coinbase": false,
"sequence": 4294967293
}
],
"vout": [
{
"scriptpubkey": "00140fc773ca476039acb48162e26190b3b93cda6762",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 0fc773ca476039acb48162e26190b3b93cda6762",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qplrh8jj8vqu6edypvt3xry9nhy7d5emzyj3a3h",
"value": 1619316
},
{
"scriptpubkey": "00142757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 2757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx",
"value": 120000
}
],
"size": 222,
"weight": 561,
"fee": 141,
"status": {
"confirmed": true,
"block_height": 1831820,
"block_hash": "0000000009f11ea4cd3a3393d1cc355a63ea37a5a2cbf983e34cbb7f37590eb2",
"block_time": 1599426488
}
},
{
"txid": "7944cfcd3d04c58a81aaa7067616f8ac368581a847e03447d5d2917cc75b67d2",
"version": 2,
"locktime": 1831428,
"vin": [
{
"txid": "86f3a772c109695ae67fe15e30df8929d2b71a2beef86ac51df406150d991a4b",
"vout": 0,
"prevout": {
"scriptpubkey": "00141d7aa33e35e539b16b11167627e818fd436ea907",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 1d7aa33e35e539b16b11167627e818fd436ea907",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qr4a2x034u5umz6c3zemz06qcl4pka2g8cc2qaf",
"value": 1859598
},
"scriptsig": "",
"scriptsig_asm": "",
"witness": [
"3044022020fa758e75fa4f67665b14cc50f871883a80c1c1f4f45c403e7f8c5631b8734002200ddde3f72dbe88975365777c15da85c44eaa6ba53384194b351709f015f4bf2001",
"02f0db65b4318ee91f58d1f7da51fb16c46e3a01f6f3bd07af1b8be63e525e3e45"
],
"is_coinbase": false,
"sequence": 4294967293
}
],
"vout": [
{
"scriptpubkey": "0014da239eb3f9727ccbfe73823e427fb7289e7c192a",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 da239eb3f9727ccbfe73823e427fb7289e7c192a",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qmg3eavlewf7vhlnnsglyylah9z08cxf2a0g2nv",
"value": 1739457
},
{
"scriptpubkey": "00142757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 2757c732c931d7722a6bdaf99ee9955303116520",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qyatuwvkfx8thy2ntmtuea6v42vp3zefqvll8kx",
"value": 120000
}
],
"size": 222,
"weight": 561,
"fee": 141,
"status": {
"confirmed": true,
"block_height": 1831429,
"block_hash": "00000000000000235978c8a27a20881376bb94b831d2072d80500d2350115be1",
"block_time": 1599254339
}
}
]