Completed
Branch master (890446)
by
unknown
10:34
created

TransactionBuilder::addRecipient()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
dl 0
loc 21
c 3
b 0
f 0
rs 9.0534
cc 4
eloc 11
nc 4
nop 2
1
<?php
2
3
namespace Blocktrail\SDK;
4
5
use BitWasp\BitcoinLib\BitcoinLib;
6
use BitWasp\BitcoinLib\RawTransaction;
7
use Blocktrail\SDK\Exceptions\BlocktrailSDKException;
8
9
/**
10
 * Class TransactionBuilder
11
 *
12
 * still WIP so unsure if API remains the same, keep this in mind when updating the SDK!
13
 */
14
class TransactionBuilder {
15
16
    const OP_RETURN = '6a';
17
18
    /**
19
     * @var UTXO[]
20
     */
21
    private $utxos = [];
22
23
    /**
24
     * @var array[]
25
     */
26
    private $outputs = [];
27
28
    private $changeAddress = null;
29
    private $randomizeChangeOutput = true;
30
31
    private $fee = null;
32
33
    private $validateFee = null;
34
35
    private $feeStrategy = Wallet::FEE_STRATEGY_OPTIMAL;
36
37
    public function __construct() {
38
39
    }
40
41
    /**
42
     * @param string $txId                   transactionId (hash)
43
     * @param int    $index                  index of the output being spent
44
     * @param string $value                  when NULL we'll use the data API to fetch the value
45
     * @param string $address                when NULL we'll use the data API to fetch the address
46
     * @param string $scriptPubKey           as HEX, when NULL we'll use the data API to fetch the scriptpubkey
47
     * @param string $path                   when NULL we'll use the API to determine the path for the specified address
48
     * @param string $redeemScript           when NULL we'll use the path to determine the redeemscript
49
     * @return $this
50
     */
51
    public function spendOutput($txId, $index, $value = null, $address = null, $scriptPubKey = null, $path = null, $redeemScript = null) {
52
        $this->utxos[] = new UTXO($txId, $index, $value, $address, $scriptPubKey, $path, $redeemScript);
53
54
        return $this;
55
    }
56
57
    /**
58
     * @return UTXO[]
59
     */
60
    public function getUtxos() {
61
        return $this->utxos;
62
    }
63
64
    /**
65
     * @param string $address
66
     * @param int    $value
67
     * @return $this
68
     * @throws \Exception
69
     */
70
    public function addRecipient($address, $value) {
71
        if (!BitcoinLib::validate_address($address)) {
72
            throw new \Exception("Invalid address [{$address}]");
73
        }
74
75
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
76
        if (!is_int($value)) {
77
            throw new \Exception("Values should be in Satoshis (int)");
78
        }
79
80
        if ($value <= Blocktrail::DUST) {
81
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
82
        }
83
84
        $this->addOutput([
85
            'address' => $address,
86
            'value' => $value
87
        ]);
88
89
        return $this;
90
    }
91
92
    /**
93
     * add a 'raw' output, normally addRecipient or addOpReturn should be used
94
     *
95
     * @param array $output     [value => int, address => string]
96
     *                          or [value => int, scriptPubKey => string] (scriptPubKey should be hex)
97
     * @return $this
98
     */
99
    public function addOutput($output) {
100
        $this->outputs[] = $output;
101
102
        return $this;
103
    }
104
105
    /**
106
     * @param $idx
107
     * @param $output
108
     * @return $this
109
     */
110
    public function replaceOutput($idx, $output) {
111
        $this->outputs[$idx] = $output;
112
113
        return $this;
114
    }
115
116
    /**
117
     * @param $idx
118
     * @param $value
119
     * @return $this
120
     * @throws \Exception
121
     */
122
    public function updateOutputValue($idx, $value) {
123
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
124
        if (!is_int($value)) {
125
            throw new \Exception("Values should be in Satoshis (int)");
126
        }
127
128
        if ($value <= Blocktrail::DUST) {
129
            throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
130
        }
131
132
        if (!isset($this->outputs[$idx])) {
133
            throw new \Exception("No output for index [{$idx}]");
134
        }
135
136
        $this->outputs[$idx]['value'] = $value;
137
138
        return $this;
139
    }
140
141
    /**
142
     * add OP_RETURN output
143
     *
144
     * $data will be bin2hex and will be prefixed with a proper OP_PUSHDATA
145
     *
146
     * @param string $data
147
     * @param bool   $allowNonStandard when TRUE will allow scriptPubKey > 40 bytes (so $data > 39 bytes)
148
     * @return $this
149
     * @throws BlocktrailSDKException
150
     */
151
    public function addOpReturn($data, $allowNonStandard = false) {
152
        $pushdata = RawTransaction::pushdata(bin2hex($data));
153
154
        if (!$allowNonStandard && strlen($pushdata) / 2 > 40) {
155
            throw new BlocktrailSDKException("OP_RETURN data should be <= 39 bytes to remain standard!");
156
        }
157
158
        $this->addOutput([
159
            'scriptPubKey' => self::OP_RETURN . RawTransaction::pushdata(bin2hex($data)),
160
            'value' => 0
161
        ]);
162
163
        return $this;
164
    }
165
166
    /**
167
     * @return array
168
     */
169
    public function getOutputs() {
170
        return $this->outputs;
171
    }
172
173
    /**
174
     * set change address
175
     *
176
     * @param string $address
177
     * @return $this
178
     */
179
    public function setChangeAddress($address) {
180
        $this->changeAddress = $address;
181
182
        return $this;
183
    }
184
185
    /**
186
     * @return string|null
187
     */
188
    public function getChangeAddress() {
189
        return $this->changeAddress;
190
    }
191
192
    /**
193
     * @param string $feeStrategy
194
     * @return $this
195
     * @throws BlocktrailSDKException
196
     */
197
    public function setFeeStrategy($feeStrategy) {
198
        $this->feeStrategy = $feeStrategy;
199
200
        if (!in_array($feeStrategy, [Wallet::FEE_STRATEGY_BASE_FEE, Wallet::FEE_STRATEGY_OPTIMAL, Wallet::FEE_STRATEGY_LOW_PRIORITY])) {
201
            throw new BlocktrailSDKException("Unknown feeStrategy [{$feeStrategy}]");
202
        }
203
204
        return $this;
205
    }
206
207
    /**
208
     * @return string
209
     */
210
    public function getFeeStrategy() {
211
        return $this->feeStrategy;
212
    }
213
214
    /**
215
     * @param bool $randomizeChangeOutput
216
     * @return $this
217
     */
218
    public function randomizeChangeOutput($randomizeChangeOutput = true) {
219
        $this->randomizeChangeOutput = $randomizeChangeOutput;
220
221
        return $this;
222
    }
223
224
    /**
225
     * @return bool
226
     */
227
    public function shouldRandomizeChangeOuput() {
228
        return $this->randomizeChangeOutput;
229
    }
230
231
    /**
232
     * set desired fee (normally automatically calculated)
233
     *
234
     * @param int $value
235
     * @return $this
236
     */
237
    public function setFee($value) {
238
        // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
239
        if (!is_int($value)) {
240
            throw new \Exception("Fee should be in Satoshis (int) - can be 0");
241
        }
242
243
        $this->fee = $value;
244
245
        return $this;
246
    }
247
248
    /**
249
     * @return int|null
250
     */
251
    public function getFee() {
252
        return $this->fee;
253
    }
254
255
    /**
256
     * @param int $fee
257
     * @return $this
258
     */
259
    public function validateFee($fee) {
260
        $this->validateFee = $fee;
261
262
        return $this;
263
    }
264
265
    /**
266
     * @return int|null
267
     */
268
    public function getValidateFee() {
269
        return $this->validateFee;
270
    }
271
}
272