-
Notifications
You must be signed in to change notification settings - Fork 301
Expand file tree
/
Copy pathtransaction.ts
More file actions
187 lines (169 loc) · 5.73 KB
/
transaction.ts
File metadata and controls
187 lines (169 loc) · 5.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/**
* Ethereum transaction model. This is the base model for all ethereum based coins (Celo, ETC, RSK, ETH)
*/
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import EthereumCommon from '@ethereumjs/common';
import {
BaseKey,
Entry,
BaseTransaction,
TransactionType,
InvalidTransactionError,
SigningError,
} from '@bitgo/sdk-core';
import { KeyPair } from './keyPair';
import { EthLikeTransactionData, TxData } from './iface';
import { EthTransactionData } from './types';
import { classifyTransaction, decodeTransferData, getToken, hasSignature, toStringSig } from './utils';
const UNSUPPORTED_COIN_NAME = 'unsupported';
export class Transaction extends BaseTransaction {
protected _id: string; // The transaction id as seen in the blockchain
protected _inputs: Entry[];
protected _outputs: Entry[];
protected _signatures: string[];
protected _type: TransactionType;
protected _common: EthereumCommon;
protected _transactionData?: EthLikeTransactionData;
/**
* return a new Transaction initialized with the serialized tx string
*
* @param coinConfig The coin configuration object
* @param common network commons
* @param serializedTx The serialized tx string with which to initialize the transaction
* @param isFirstSigner whether the transaction is being signed by the first signer
* @returns a new transaction object
*/
public static fromSerialized(
coinConfig: Readonly<CoinConfig>,
common: EthereumCommon,
serializedTx: string,
isFirstSigner?: boolean
): Transaction {
return new Transaction(
coinConfig,
common,
EthTransactionData.fromSerialized(serializedTx, common).toJson(),
isFirstSigner
);
}
/**
* Public constructor.
*
* @param {Readonly<CoinConfig>} coinConfig
* @param common the network commons
* @param {TxData} txData The object transaction data or encoded transaction data
* @param {boolean} isFirstSigner whether the transaction is being signed by the first signer
*/
constructor(coinConfig: Readonly<CoinConfig>, common: EthereumCommon, txData?: TxData, isFirstSigner?: boolean) {
super(coinConfig);
this._common = common;
if (txData) {
this.setTransactionData(txData, isFirstSigner);
}
}
/**
* Set the transaction data
*
* @param {TxData} txData The transaction data to set
* @param {boolean} isFirstSigner Whether the transaction is being signed by the first signer
*/
setTransactionData(txData: TxData, isFirstSigner?: boolean): void {
this._transactionData = EthTransactionData.fromJson(txData, this._common);
this.updateFields(isFirstSigner);
}
/**
* Update the internal fields based on the currently set transaction data, if there is any
*/
protected updateFields(isFirstSigner?: boolean): void {
if (!this._transactionData) {
return;
}
const txData = this._transactionData.toJson();
if (txData.id) {
this._id = txData.id;
}
this._type = classifyTransaction(txData.data, this._coinConfig.name);
// reset arrays to empty to ensure that they are only set with one set of fresh values
this._inputs = [];
this._outputs = [];
this._signatures = [];
if (hasSignature(txData)) {
this._signatures.push(toStringSig({ v: txData.v!, r: txData.r!, s: txData.s! }));
}
// only send transactions have inputs / outputs / signatures to parse
if (
this._type === TransactionType.Send ||
this._type === TransactionType.SendERC721 ||
this._type === TransactionType.SendERC1155
) {
const { to, amount, tokenContractAddress, signature } = decodeTransferData(txData.data, isFirstSigner);
let coinName: string;
if (tokenContractAddress) {
const token = getToken(tokenContractAddress, this._coinConfig.network, this._coinConfig.family);
coinName = token ? token.name : UNSUPPORTED_COIN_NAME;
} else {
coinName = this._coinConfig.name;
}
this.outputs.push({
address: to,
value: amount,
coin: coinName,
});
this.inputs.push({
address: txData.to!, // the sending wallet contract is the recipient of the outer transaction
value: amount,
coin: coinName,
});
if (signature !== '0x') {
this._signatures.push(signature);
}
}
}
/**
* Set the transaction type
*
* @param {TransactionType} transactionType The transaction type to be set
*/
setTransactionType(transactionType: TransactionType): void {
this._type = transactionType;
}
/** @inheritdoc */
canSign(key: BaseKey): boolean {
// TODO: implement this validation for the ethereum network
return true;
}
/**
* Sign the transaction with the provided key. It does not check if the signer is allowed to sign
* it or not.
*
* @param {KeyPair} keyPair The key to sign the transaction with
*/
async sign(keyPair: KeyPair): Promise<void> {
if (!this._transactionData) {
throw new InvalidTransactionError('No transaction data to sign');
}
if (!keyPair.getKeys().prv) {
throw new SigningError('Missing private key');
}
await this._transactionData.sign(keyPair);
const txData = this._transactionData.toJson();
if (txData.id) {
this._id = txData.id;
}
this._signatures.push(toStringSig({ v: txData.v!, r: txData.r!, s: txData.s! }));
}
/** @inheritdoc */
toBroadcastFormat(): string {
if (this._transactionData) {
return this._transactionData.toSerialized();
}
throw new InvalidTransactionError('No transaction data to format');
}
/** @inheritdoc */
toJson(): TxData {
if (this._transactionData) {
return this._transactionData.toJson();
}
throw new InvalidTransactionError('Empty transaction');
}
}