1 | const { Hex, Address, PrivateKey, Drip } = require('./type');
|
2 | const { rlpEncode, sha3, ecdsaSign, ecdsaRecover, publicKeyToAddress } = require('./sign');
|
3 |
|
4 | function throwError(...args) {
|
5 | throw new Error(...args);
|
6 | }
|
7 |
|
8 | class Transaction {
|
9 | /**
|
10 | * @param options {object}
|
11 | * @param options.from {string} - The address the transaction is send from.
|
12 | * @param options.nonce {string|number} - This allows to overwrite your own pending transactions that use the same nonce.
|
13 | * @param options.gasPrice {string|number} - The gasPrice used for each paid gas.
|
14 | * @param options.gas {string|number} - The gas provided for the transaction execution. It will return unused gas.
|
15 | * @param [options.to] {string} - The address the transaction is directed to.
|
16 | * @param [options.value] {string|number|BigNumber} - the value sent with this transaction
|
17 | * @param [options.data=''] {string|Buffer} - The compiled code of a contract OR the hash of the invoked method signature and encoded parameters.
|
18 | * @return {object} Formatted send transaction options object.
|
19 | */
|
20 | static sendOptions({ from, nonce, gasPrice, gas, to, value, data }) {
|
21 | return {
|
22 | from: from !== undefined ? Address(from) : throwError(`'from' is required and should match 'Address', got ${from}`),
|
23 | nonce: nonce !== undefined ? Hex.fromNumber(nonce) : throwError(`'nonce' is required and should match 'uint', got ${nonce}`),
|
24 | gasPrice: gasPrice !== undefined ? Drip(gasPrice) : throwError(`'gasPrice' is required and should match 'Drip', got ${gasPrice}`),
|
25 | gas: gas !== undefined ? Hex.fromNumber(gas) : throwError(`'gas' is required and should match 'uint', got ${gas}`),
|
26 | to: to !== undefined ? Address(to) : undefined,
|
27 | value: value !== undefined ? Drip(value) : undefined,
|
28 | data: data !== undefined ? Hex(data) : Hex(''),
|
29 | };
|
30 | }
|
31 |
|
32 | /**
|
33 | * @param options {object}
|
34 | * @param [options.from] {string} - The address the transaction is sent from.
|
35 | * @param [options.nonce] {string|number} - The caller nonce (transaction count).
|
36 | * @param [options.gasPrice] {string|number} - The gasPrice used for each paid gas.
|
37 | * @param [options.gas] {string|number} - The gas provided for the transaction execution. `call` consumes zero gas, but this parameter may be needed by some executions.
|
38 | * @param options.to {string} - The address the transaction is directed to.
|
39 | * @param [options.value] {string|number|BigNumber} - Integer of the value sent with this transaction.
|
40 | * @param [options.data] {string|Buffer} - Hash of the method signature and encoded parameters.
|
41 | * @return {object} Formatted call contract options object.
|
42 | */
|
43 | static callOptions({ from, nonce, gasPrice, gas, to, value, data }) {
|
44 | return {
|
45 | from: from !== undefined ? Address(from) : undefined,
|
46 | nonce: nonce !== undefined ? Hex.fromNumber(nonce) : undefined,
|
47 | gasPrice: gasPrice !== undefined ? Drip(gasPrice) : undefined,
|
48 | gas: gas !== undefined ? Hex.fromNumber(gas) : undefined,
|
49 | to: to !== undefined ? Address(to) : throwError(`'to' is required and should match 'Address', got ${to}`),
|
50 | value: value !== undefined ? Drip(value) : undefined,
|
51 | data: data !== undefined ? Hex(data) : undefined,
|
52 | };
|
53 | }
|
54 |
|
55 | /**
|
56 | * @param options {object}
|
57 | * @param [options.from] {string} - The address the transaction is sent from.
|
58 | * @param [options.nonce] {string|number} - The caller nonce (transaction count).
|
59 | * @param [options.gasPrice] {string|number} - The gasPrice used for each paid gas.
|
60 | * @param [options.gas] {string|number} - The gas provided for the transaction execution. `call` consumes zero gas, but this parameter may be needed by some executions.
|
61 | * @param [options.to] {string} - The address the transaction is directed to.
|
62 | * @param [options.value] {string|number|BigNumber} - Integer of the value sent with this transaction.
|
63 | * @param [options.data] {string|Buffer} - Hash of the method signature and encoded parameters.
|
64 | * @return {object} Formatted call contract options object.
|
65 | */
|
66 | static estimateOptions({ from, nonce, gasPrice, gas, to, value, data }) {
|
67 | return {
|
68 | from: from !== undefined ? Address(from) : undefined,
|
69 | nonce: nonce !== undefined ? Hex.fromNumber(nonce) : undefined,
|
70 | gasPrice: gasPrice !== undefined ? Drip(gasPrice) : undefined,
|
71 | gas: gas !== undefined ? Hex.fromNumber(gas) : undefined,
|
72 | to: to !== undefined ? Address(to) : undefined,
|
73 | value: value !== undefined ? Drip(value) : undefined,
|
74 | data: data !== undefined ? Hex(data) : undefined,
|
75 | };
|
76 | }
|
77 |
|
78 | /**
|
79 | * @param options {object}
|
80 | * @param options.nonce {string|number} - This allows to overwrite your own pending transactions that use the same nonce.
|
81 | * @param options.gasPrice {string|number|BigNumber} - The price of gas for this transaction in drip.
|
82 | * @param options.gas {string|number} - The amount of gas to use for the transaction (unused gas is refunded).
|
83 | * @param [options.to=null] {string} - The destination address of the message, left undefined for a contract-creation transaction.
|
84 | * @param [options.value=0] {string|number|BigNumber} - The value transferred for the transaction in drip, also the endowment if it’s a contract-creation transaction.
|
85 | * @param [options.data=''] {string|Buffer} - Either a ABI byte string containing the data of the function call on a contract, or in the case of a contract-creation transaction the initialisation code.
|
86 | * @param [options.r=null] {string|Buffer} - ECDSA signature r
|
87 | * @param [options.s=null] {string|Buffer} - ECDSA signature s
|
88 | * @param [options.v=null] {string|number} - ECDSA recovery id
|
89 | * @return {object} Formatted sign transaction options object.
|
90 | */
|
91 | static rawOptions({ nonce, gasPrice, gas, to, value, data, r, s, v }) {
|
92 | return {
|
93 | nonce: nonce !== undefined ? Hex.fromNumber(nonce) : throwError(`'nonce' is required and should match 'uint', got ${nonce}`),
|
94 | gasPrice: gasPrice !== undefined ? Drip(gasPrice) : throwError(`'gasPrice' is required and should match 'Drip', got ${gasPrice}`),
|
95 | gas: gas !== undefined ? Hex.fromNumber(gas) : throwError(`'gas' is required and should match 'uint', got ${gas}`),
|
96 | to: to !== undefined ? Address(to) : Hex(null),
|
97 | value: value !== undefined ? Drip(value) : Drip(0),
|
98 | data: data !== undefined ? Hex(data) : Hex(''),
|
99 | r: r !== undefined ? Hex(r) : undefined,
|
100 | s: s !== undefined ? Hex(s) : undefined,
|
101 | v: v !== undefined ? Hex(v) : undefined,
|
102 | };
|
103 | }
|
104 |
|
105 | /**
|
106 | * Signs a transaction. This account needs to be unlocked.
|
107 | *
|
108 | * @param options {object} - See `Transaction.rawOptions`
|
109 | * @return {Transaction}
|
110 | */
|
111 | constructor(options) {
|
112 | Object.assign(this, Transaction.rawOptions(options));
|
113 | }
|
114 |
|
115 | /**
|
116 | * Getter of transaction hash include signature.
|
117 | *
|
118 | * > Note: calculate every time.
|
119 | *
|
120 | * @return {string|undefined} If transaction has r,s,v return hex string, else return undefined.
|
121 | */
|
122 | get hash() {
|
123 | try {
|
124 | return Hex(sha3(this.encode(true)));
|
125 | } catch (e) {
|
126 | return undefined;
|
127 | }
|
128 | }
|
129 |
|
130 | /**
|
131 | * Getter of sender address.
|
132 | *
|
133 | * > Note: calculate every time.
|
134 | *
|
135 | * @return {string|undefined} If ECDSA recover success return address, else return undefined.
|
136 | */
|
137 | get from() {
|
138 | try {
|
139 | const publicKey = ecdsaRecover(sha3(this.encode(false)), {
|
140 | r: Hex.toBuffer(this.r),
|
141 | s: Hex.toBuffer(this.s),
|
142 | v: Number(this.v),
|
143 | });
|
144 | return Hex(publicKeyToAddress(publicKey));
|
145 | } catch (e) {
|
146 | return undefined;
|
147 | }
|
148 | }
|
149 |
|
150 | /**
|
151 | * Sign transaction and set 'r','s','v'.
|
152 | *
|
153 | * @param privateKey {string} - Private key hex string.
|
154 | */
|
155 | sign(privateKey) {
|
156 | const { r, s, v } = ecdsaSign(sha3(this.encode(false)), Hex.toBuffer(PrivateKey(privateKey)));
|
157 | this.r = Hex(r);
|
158 | this.s = Hex(s);
|
159 | this.v = Hex(v);
|
160 | }
|
161 |
|
162 | /**
|
163 | * Encode rlp.
|
164 | *
|
165 | * @param [includeSignature=false] {boolean} - Whether or not to include the signature.
|
166 | * @return {Buffer}
|
167 | */
|
168 | encode(includeSignature = false) {
|
169 | const raw = [this.nonce, this.gasPrice, this.gas, this.to, this.value, this.data]; // ordered
|
170 | if (includeSignature) {
|
171 | if (this.v === undefined) {
|
172 | throwError('`v` is required and should match `Hex`');
|
173 | }
|
174 | if (this.r === undefined) {
|
175 | throwError('`r` is required and should match `Hex`');
|
176 | }
|
177 | if (this.s === undefined) {
|
178 | throwError('`s` is required and should match `Hex`');
|
179 | }
|
180 | raw.push(this.v, this.r, this.s); // ordered
|
181 | }
|
182 | return rlpEncode(raw.map(Hex.toBuffer));
|
183 | }
|
184 |
|
185 | /**
|
186 | * Get the raw tx hex string.
|
187 | *
|
188 | * @return {Buffer}
|
189 | */
|
190 | serialize() {
|
191 | return Hex(this.encode(true));
|
192 | }
|
193 | }
|
194 |
|
195 | module.exports = Transaction;
|