/
Transaction.ts
224 lines (203 loc) · 7.59 KB
/
Transaction.ts
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import { Interface } from '@ethersproject/abi'
import { AddressZero } from '@ethersproject/constants'
import { Logger } from '@ethersproject/logger'
import * as TrustlinesContractsAbi from '@trustlines/trustlines-contracts-abi'
import { BigNumber } from 'bignumber.js'
import { TLProvider } from './providers/TLProvider'
import { TLSigner } from './signers/TLSigner'
import utils from './utils'
import {
PaidDelegationFeesRaw,
RawTxObject,
TransactionStatusObject,
TxFeesAmounts,
TxFeesRaw,
TxObjectInternal,
TxOptionsInternal
} from './typings'
import { CurrencyNetwork } from './CurrencyNetwork'
// Ethers will otherwise warn for every call on `updateTrustline` due to function overloading
// see https://github.com/ethers-io/ethers.js/issues/407
Logger.setLogLevel(Logger.levels.ERROR)
const ETH_DECIMALS = 18
export const GAS_LIMIT_MULTIPLIER = 1.2
// Value taken from the contracts gas tests
export const GAS_LIMIT_IDENTITY_OVERHEAD = new BigNumber(50_000)
export const GAS_LIMIT_VALUE_TRANSACTION = new BigNumber(21_000)
.plus(GAS_LIMIT_IDENTITY_OVERHEAD.multipliedBy(GAS_LIMIT_MULTIPLIER))
.integerValue(BigNumber.ROUND_DOWN)
export const GAS_LIMIT_DEFAULT_CONTRACT_TRANSACTION = new BigNumber(600_000)
/**
* The Transaction class contains functions that are needed for Ethereum transactions.
*/
export class Transaction {
private signer: TLSigner
private provider: TLProvider
private currencyNetwork: CurrencyNetwork
constructor(params: {
signer: TLSigner
provider: TLProvider
currencyNetwork: CurrencyNetwork
}) {
this.signer = params.signer
this.provider = params.provider
this.currencyNetwork = params.currencyNetwork
}
/**
* Returns transaction fees and the raw transaction object for calling a contract function.
* @param userAddress address of user that calls the contract function
* @param contractAddress address of deployed contract
* @param contractName name of deployed contract
* @param functionName name of contract function
* @param args arguments of function in same order as in contract
* @param options.gasPrice (optional)
* @param options.gasLimit (optional)
* @param options.value (optional)
* @param options.baseFee (optional) base fees to be used notably for meta-transactions.
* @param options.currencyNetworkOfFees (optional) currency network of fees for a meta transaction.
* @returns An ethereum transaction object and the estimated transaction fees in ETH.
*/
public async prepareContractTransaction(
userAddress: string,
contractAddress: string,
contractName: string,
functionName: string,
args: any[],
options: TxOptionsInternal = {}
): Promise<TxObjectInternal> {
const abi = new Interface(TrustlinesContractsAbi[contractName].abi)
const rawTx: RawTxObject = {
data: abi.encodeFunctionData(functionName, args),
from: userAddress,
to: contractAddress,
gasLimit: options.gasLimit || GAS_LIMIT_DEFAULT_CONTRACT_TRANSACTION,
gasPrice: options.gasPrice || undefined,
baseFee: options.baseFee || undefined,
currencyNetworkOfFees: options.currencyNetworkOfFees || undefined,
value: options.value || new BigNumber(0)
}
const preparedTx = await this.signer.prepareTransaction(rawTx)
return {
txFees: await this.formatTxFeesToAmount(preparedTx.txFees),
rawTx: preparedTx.rawTx
}
}
/**
* Returns transaction fees and raw transaction object for transferring ETH.
* @param senderAddress address of user sending the transfer
* @param receiverAddress address of user receiving the transfer
* @param rawValue transfer amount in wei
* @param options.gasPrice (optional)
* @param options.gasLimit (optional)
* @param options.baseFee (optional) base fees to be used notably for meta-transactions.
* @param options.currencyNetworkOfFees (optional) currency network of fees for a meta transaction.
* @returns An ethereum transaction object containing and the estimated transaction fees in ETH.
*/
public async prepareValueTransaction(
senderAddress: string,
receiverAddress: string,
rawValue: BigNumber,
options: TxOptionsInternal = {}
): Promise<TxObjectInternal> {
const rawTx: RawTxObject = {
from: senderAddress,
to: receiverAddress,
gasLimit: options.gasLimit || GAS_LIMIT_VALUE_TRANSACTION,
gasPrice: options.gasPrice || undefined,
baseFee: options.baseFee || undefined,
currencyNetworkOfFees: options.currencyNetworkOfFees || undefined,
value: rawValue
}
const preparedTx = await this.signer.prepareTransaction(rawTx)
return {
txFees: await this.formatTxFeesToAmount(preparedTx.txFees),
rawTx: preparedTx.rawTx
}
}
/**
* Signs and sends the given transaction object.
* @param rawTx Raw transaction object.
*/
public async confirm(rawTx: RawTxObject): Promise<string> {
return this.signer.confirm(rawTx)
}
/**
* Get the status of a sent tx either via txHash or via rawTx for a meta-tx
* @param tx the hash of the transaction / meta-tx or raw transaction object from which a meta-tx was built.
*/
public async getTxStatus(
tx: string | RawTxObject
): Promise<TransactionStatusObject> {
return this.signer.getTxStatus(tx)
}
/**
* Get the effective delegation fees via enveloping transaction hash
* @param txHash the hash of the transaction in which fees were paid
* @returns a list of the delegation fees applied within the transaction paid by loaded user
*/
public async getAppliedDelegationFees(
txHash: string
): Promise<TxFeesAmounts[]> {
const Url = utils.buildUrl(`/delegation-fees/`, {
query: {
transactionHash: txHash
}
})
let paidFeesList = await this.provider.fetchEndpoint<
PaidDelegationFeesRaw[]
>(Url)
// We might receive fees not paid by the loaded user so we filter them out
paidFeesList = paidFeesList.filter(
async paidFee => paidFee.feeSender === (await this.signer.getAddress())
)
return Promise.all(
paidFeesList.map(async paidFees =>
this.formatTxFeesToAmount({
totalFee: paidFees.totalFee,
feeRecipient: paidFees.feeRecipient,
currencyNetworkOfFees: paidFees.currencyNetworkOfFees
})
)
)
}
/**
* Formats the tx fees and finds the currency network decimals to use in case of meta-tx fees
* @param txFees
*/
private async formatTxFeesToAmount(
txFees: TxFeesRaw
): Promise<TxFeesAmounts> {
// 18 decimals for regular tx fees in ether
let feeDecimals = ETH_DECIMALS
// Only possible if the currencyNetwork is set and is not the zero address.
if (
txFees.currencyNetworkOfFees &&
txFees.currencyNetworkOfFees !== AddressZero
) {
feeDecimals = (
await this.currencyNetwork.getDecimals(txFees.currencyNetworkOfFees)
).networkDecimals
}
return {
gasPrice:
txFees.gasPrice !== undefined
? utils.formatToAmount(txFees.gasPrice, feeDecimals)
: null,
gasLimit:
txFees.gasLimit !== undefined
? utils.formatToAmount(txFees.gasLimit, 0)
: null,
baseFee:
txFees.baseFee !== undefined
? utils.formatToAmount(txFees.baseFee, feeDecimals)
: null,
totalFee: utils.formatToAmount(txFees.totalFee, feeDecimals),
feeRecipient: txFees.feeRecipient || null,
// TODO remove handling of AddressZero. Only there for backwards compatibility.
currencyNetworkOfFees:
(txFees.currencyNetworkOfFees !== AddressZero
? txFees.currencyNetworkOfFees
: null) || null
}
}
}