UNPKG

8.98 kBJavaScriptView Raw
1const { Hex, Address, PrivateKey, Drip } = require('./type');
2const { rlpEncode, sha3, ecdsaSign, ecdsaRecover, publicKeyToAddress } = require('./sign');
3
4function throwError(...args) {
5 throw new Error(...args);
6}
7
8class 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
195module.exports = Transaction;