@exodus/ethereum-api 2.15.1 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/package/package.json +33 -0
  2. package/package/src/eth-like-util.js +147 -0
  3. package/package/src/etherscan/account.js +49 -0
  4. package/package/src/etherscan/index.js +48 -0
  5. package/package/src/etherscan/logs.js +16 -0
  6. package/package/src/etherscan/proxy.js +47 -0
  7. package/package/src/etherscan/request.js +25 -0
  8. package/package/src/etherscan/ws.js +88 -0
  9. package/package/src/exodus-eth-server/api.js +242 -0
  10. package/package/src/exodus-eth-server/index.js +36 -0
  11. package/package/src/exodus-eth-server/ws.js +108 -0
  12. package/package/src/fee-monitor/avalanchec.js +12 -0
  13. package/package/src/fee-monitor/bsc.js +12 -0
  14. package/package/src/fee-monitor/ethereum.js +13 -0
  15. package/package/src/fee-monitor/ethereumclassic.js +12 -0
  16. package/package/src/fee-monitor/fantom.js +12 -0
  17. package/package/src/fee-monitor/harmony.js +12 -0
  18. package/package/src/fee-monitor/index.js +7 -0
  19. package/package/src/fee-monitor/polygon.js +12 -0
  20. package/package/src/gas-estimation.js +103 -0
  21. package/package/src/get-balances.js +38 -0
  22. package/package/src/index.js +11 -0
  23. package/package/src/simulate-tx/fetch-tx-preview.js +21 -0
  24. package/package/src/simulate-tx/index.js +2 -0
  25. package/package/src/simulate-tx/simulate-eth-tx.js +86 -0
  26. package/package/src/staking/fantom-staking.js +115 -0
  27. package/package/src/staking/index.js +2 -0
  28. package/package/src/staking/matic-staking.js +159 -0
  29. package/package/src/tx-log/__tests__/assets-for-test-helper.js +30 -0
  30. package/package/src/tx-log/__tests__/bsc-history-return-values-for-test-helper.js +94 -0
  31. package/package/src/tx-log/__tests__/bsc-monitor.integration.test.js +167 -0
  32. package/package/src/tx-log/__tests__/bsc-monitor.test.js +143 -0
  33. package/package/src/tx-log/__tests__/ethereum-history-return-values-for-test-helper.js +357 -0
  34. package/package/src/tx-log/__tests__/ethereum-history-unknown-token-helper.js +612 -0
  35. package/package/src/tx-log/__tests__/ethereum-monitor.integration.test.js +163 -0
  36. package/package/src/tx-log/__tests__/ethereum-monitor.test.js +211 -0
  37. package/package/src/tx-log/__tests__/monitor-test-helper.js +39 -0
  38. package/package/src/tx-log/__tests__/steth-monitor.integration.test.js +91 -0
  39. package/package/src/tx-log/__tests__/uniswap-monitor.integration.test.js +86 -0
  40. package/package/src/tx-log/__tests__/uniswap-monitor.test.js +158 -0
  41. package/package/src/tx-log/__tests__/uniswap-return-values-for-test-helper.js +193 -0
  42. package/package/src/tx-log/ethereum-monitor.js +293 -0
  43. package/package/src/tx-log/index.js +1 -0
  44. package/package/src/tx-log/ws-updates.js +75 -0
  45. package/package/src/websocket/index.android.js +2 -0
  46. package/package/src/websocket/index.ios.js +2 -0
  47. package/package/src/websocket/index.js +23 -0
  48. package/package.json +4 -3
  49. package/src/eth-like-util.js +72 -1
  50. package/src/exodus-eth-server/api.js +2 -2
  51. package/src/simulate-tx/simulate-eth-tx.js +43 -25
  52. package/src/tx-log/ethereum-monitor.js +9 -11
@@ -0,0 +1,36 @@
1
+ import { create } from './api'
2
+
3
+ const EXODUS_ETH_SERVER_URL = 'https://geth.a.exodus.io/wallet/v1/'
4
+ const EXODUS_ETC_SERVER_URL = 'https://getc.a.exodus.io/wallet/v1/'
5
+ const EXODUS_BSC_SERVER_URL = 'https://bsc.a.exodus.io/wallet/v1/'
6
+ const EXODUS_POLYGON_SERVER_URL = 'https://polygon.a.exodus.io/wallet/v1/'
7
+ const EXODUS_AVAXC_SERVER_URL = 'https://avax-c.a.exodus.io/wallet/v1/'
8
+ const EXODUS_FTM_SERVER_URL = 'https://fantom.a.exodus.io/wallet/v1/'
9
+ const EXODUS_HARMONY_SERVER_URL = 'https://harmony.a.exodus.io/wallet/v1/'
10
+ // allow self-signed certs
11
+ // process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
12
+ export const eth = create(EXODUS_ETH_SERVER_URL)
13
+ export const etc = create(EXODUS_ETC_SERVER_URL)
14
+ export const bsc = create(EXODUS_BSC_SERVER_URL)
15
+ export const polygon = create(EXODUS_POLYGON_SERVER_URL)
16
+ export const avaxc = create(EXODUS_AVAXC_SERVER_URL)
17
+ export const ftm = create(EXODUS_FTM_SERVER_URL)
18
+ export const harmony = create(EXODUS_HARMONY_SERVER_URL)
19
+
20
+ // exported for in-library use only.
21
+ export const serverMap = {
22
+ ethereum: eth,
23
+ ethereumclassic: etc,
24
+ bsc,
25
+ matic: polygon,
26
+ avalanchec: avaxc,
27
+ fantommainnet: ftm,
28
+ harmonymainnet: harmony,
29
+ }
30
+
31
+ export function getServer(asset) {
32
+ const baseAssetName = asset.baseAsset.name
33
+ const server = serverMap[baseAssetName]
34
+ if (!server) throw new Error(`unsupported base asset ${baseAssetName}`)
35
+ return server
36
+ }
@@ -0,0 +1,108 @@
1
+ import ms from 'ms'
2
+ import { EventEmitter } from 'events'
3
+ import WebSocket from '../websocket'
4
+
5
+ const RECONNECT_INTERVAL = ms('10s')
6
+ const PING_INTERVAL = ms('10s')
7
+ const MAX_RECONNECT_DELAY = ms('15s')
8
+
9
+ export default function createWebSocket(getURL) {
10
+ const addresses = new Set()
11
+ const events = new EventEmitter()
12
+ let ws
13
+ let pingIntervalId = null
14
+ let opened = false
15
+ let isWSOpened = false
16
+ let openTimeoutId
17
+
18
+ function subscribeAddress(address) {
19
+ const data = JSON.stringify({ type: 'subscribe-address', address })
20
+ ws.send(data)
21
+ }
22
+
23
+ function subscribeGasPrice() {
24
+ const data = JSON.stringify({ type: 'subscribe-gasprice' })
25
+ ws.send(data)
26
+ }
27
+
28
+ function clearPing() {
29
+ clearInterval(pingIntervalId)
30
+ pingIntervalId = null
31
+ }
32
+
33
+ function onMessage(data) {
34
+ data = JSON.parse(data)
35
+ switch (data.type) {
36
+ case 'address':
37
+ events.emit(`address-${data.address}`)
38
+ break
39
+ case 'gasprice':
40
+ events.emit('gasprice', data.gasprice)
41
+ break
42
+ }
43
+ }
44
+
45
+ function open() {
46
+ opened = true
47
+ clearTimeout(openTimeoutId)
48
+ if (ws) return
49
+
50
+ ws = new WebSocket(getURL())
51
+ ws.onerror = (e) => {}
52
+ ws.onmessage = (e) => {
53
+ try {
54
+ onMessage(e.data)
55
+ } catch (err) {}
56
+ }
57
+ ws.onopen = () => {
58
+ for (const address of addresses.values()) subscribeAddress(address)
59
+ subscribeGasPrice()
60
+ if (typeof ws.ping === 'function') {
61
+ pingIntervalId = setInterval(() => ws.ping(), PING_INTERVAL)
62
+ } else {
63
+ console.warn('Client side ping not available')
64
+ }
65
+ isWSOpened = true
66
+ events.emit('open')
67
+ }
68
+ ws.onclose = () => {
69
+ ws = null
70
+ isWSOpened = false
71
+ clearPing()
72
+ if (opened) {
73
+ openTimeoutId = setTimeout(open, reconnectDelay())
74
+ }
75
+ events.emit('close')
76
+ }
77
+ }
78
+
79
+ function close() {
80
+ opened = false
81
+ isWSOpened = false
82
+ clearTimeout(openTimeoutId)
83
+ if (!ws) return
84
+ ws.onerror = null
85
+ ws.onmessage = null
86
+ ws.onopen = null
87
+ ws.onclose = null
88
+ ws.close()
89
+ ws = null
90
+ clearPing()
91
+ events.emit('close')
92
+ }
93
+
94
+ function reconnectDelay() {
95
+ const min = RECONNECT_INTERVAL
96
+ const max = MAX_RECONNECT_DELAY
97
+ return Math.floor(Math.random() * (max - min + 1) + min)
98
+ }
99
+
100
+ function watch(address) {
101
+ if (addresses.has(address)) return
102
+ addresses.add(address)
103
+
104
+ if (ws) subscribeAddress(address)
105
+ }
106
+
107
+ return { events, open, close, watch, isCreated: () => !!ws, isOpened: () => isWSOpened }
108
+ }
@@ -0,0 +1,12 @@
1
+ import { EthereumLikeFeeMonitor } from '@exodus/ethereum-lib'
2
+ import { avaxc as avalancheServer } from '../exodus-eth-server'
3
+
4
+ export class AvalancheFeeMonitor extends EthereumLikeFeeMonitor {
5
+ constructor({ updateFee }) {
6
+ super({
7
+ updateFee,
8
+ assetName: 'avalanchec',
9
+ getGasPrice: avalancheServer.gasPrice,
10
+ })
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ import { EthereumLikeFeeMonitor } from '@exodus/ethereum-lib'
2
+ import { bsc } from '../exodus-eth-server'
3
+
4
+ export class BscFeeMonitor extends EthereumLikeFeeMonitor {
5
+ constructor({ updateFee }) {
6
+ super({
7
+ updateFee,
8
+ assetName: 'bsc',
9
+ getGasPrice: bsc.gasPrice,
10
+ })
11
+ }
12
+ }
@@ -0,0 +1,13 @@
1
+ import { EthereumLikeFeeMonitor } from '@exodus/ethereum-lib'
2
+ import { eth as ethServer } from '../exodus-eth-server'
3
+
4
+ export class EthereumFeeMonitor extends EthereumLikeFeeMonitor {
5
+ constructor({ updateFee, interval }) {
6
+ super({
7
+ updateFee,
8
+ assetName: 'ethereum',
9
+ getGasPrice: ethServer.gasPrice,
10
+ interval,
11
+ })
12
+ }
13
+ }
@@ -0,0 +1,12 @@
1
+ import { EthereumLikeFeeMonitor } from '@exodus/ethereum-lib'
2
+ import { etc as etcServer } from '../exodus-eth-server'
3
+
4
+ export class EthereumClassicFeeMonitor extends EthereumLikeFeeMonitor {
5
+ constructor({ updateFee }) {
6
+ super({
7
+ updateFee,
8
+ assetName: 'ethereumclassic',
9
+ getGasPrice: etcServer.gasPrice,
10
+ })
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ import { EthereumLikeFeeMonitor } from '@exodus/ethereum-lib'
2
+ import { ftm as fantomServer } from '../exodus-eth-server'
3
+
4
+ export class FantomFeeMonitor extends EthereumLikeFeeMonitor {
5
+ constructor({ updateFee }) {
6
+ super({
7
+ updateFee,
8
+ assetName: 'fantommainnet',
9
+ getGasPrice: fantomServer.gasPrice,
10
+ })
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ import { EthereumLikeFeeMonitor } from '@exodus/ethereum-lib'
2
+ import { harmony as harmonyServer } from '../exodus-eth-server'
3
+
4
+ export class HarmonyFeeMonitor extends EthereumLikeFeeMonitor {
5
+ constructor({ updateFee }) {
6
+ super({
7
+ updateFee,
8
+ assetName: 'harmonymainnet',
9
+ getGasPrice: harmonyServer.gasPrice,
10
+ })
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ export * from './ethereum'
2
+ export * from './ethereumclassic'
3
+ export * from './bsc'
4
+ export * from './polygon'
5
+ export * from './avalanchec'
6
+ export * from './fantom'
7
+ export * from './harmony'
@@ -0,0 +1,12 @@
1
+ import { EthereumLikeFeeMonitor } from '@exodus/ethereum-lib'
2
+ import { polygon as polygonServer } from '../exodus-eth-server'
3
+
4
+ export class PolygonFeeMonitor extends EthereumLikeFeeMonitor {
5
+ constructor({ updateFee }) {
6
+ super({
7
+ updateFee,
8
+ assetName: 'matic',
9
+ getGasPrice: polygonServer.gasPrice,
10
+ })
11
+ }
12
+ }
@@ -0,0 +1,103 @@
1
+ import BN from 'bn.js'
2
+ import * as ethUtil from '@exodus/ethereumjs-util'
3
+ import { currency2buffer, isToken } from '@exodus/ethereum-lib'
4
+ import { estimateGas, isContractAddress } from './eth-like-util'
5
+
6
+ const EXTRA_PERCENTAGE = 20
7
+
8
+ // Starting with geth v1.9.14, if gasPrice is set for eth_estimateGas call, the call allowance will
9
+ // be calculated with account's balance divided by gasPrice. If user's balance is too low,
10
+ // the gasEstimation will fail. If gasPrice is set to '0x0', the account's balance is not
11
+ // used to estimate gas.
12
+ export async function estimateGasLimit(
13
+ asset: Object,
14
+ fromAddress: string,
15
+ toAddress: string,
16
+ amount: Buffer | Object,
17
+ data: Buffer | string,
18
+ gasPrice?: string = '0x',
19
+ extraPercentage?: number = EXTRA_PERCENTAGE
20
+ ): number {
21
+ const opts = {
22
+ from: fromAddress,
23
+ to: toAddress,
24
+ value: normalizeAmount(amount),
25
+ data: Buffer.isBuffer(data) ? ethUtil.bufferToHex(data) : data,
26
+ gasPrice: normalizeGasPrice(gasPrice),
27
+ }
28
+
29
+ const estimatedGas = await estimateGas({ asset, ...opts })
30
+ return new BN(estimatedGas.slice(2), 16)
31
+ .imuln(100 + extraPercentage)
32
+ .idivn(100)
33
+ .toNumber()
34
+ }
35
+
36
+ export async function fetchGasLimit({
37
+ asset,
38
+ fromAddress,
39
+ toAddress,
40
+ txInput = '0x',
41
+ amount,
42
+ feeData = {},
43
+ bip70,
44
+ isContract,
45
+ throwOnError = true,
46
+ extraPercentage,
47
+ }) {
48
+ if (bip70?.bitpay?.data && bip70?.bitpay?.gasPrice)
49
+ return asset.name === 'ethereum' ? 65000 : 130000 // from on chain stats https://dune.xyz/queries/189123
50
+
51
+ if (!amount) amount = asset.currency.ZERO
52
+ if (!feeData.gasPrice) feeData.gasPrice = asset.baseAsset.currency.ZERO
53
+
54
+ const _isToken = isToken(asset)
55
+ if (_isToken) {
56
+ txInput = ethUtil.bufferToHex(
57
+ asset.contract.transfer.build(toAddress, amount.toBase().toString({ unit: false }))
58
+ )
59
+ amount = asset.baseAsset.currency.ZERO
60
+ toAddress = asset.contract.address
61
+ } else if (!isContract) {
62
+ if (isContract === undefined)
63
+ isContract = await isContractAddress({ asset, address: toAddress })
64
+ if (!isContract) return asset.gasLimit
65
+ }
66
+
67
+ const gasPrice = ethUtil.bufferToHex(currency2buffer(feeData.gasPrice))
68
+
69
+ try {
70
+ return await estimateGasLimit(
71
+ asset,
72
+ fromAddress,
73
+ toAddress,
74
+ amount,
75
+ txInput,
76
+ gasPrice,
77
+ extraPercentage
78
+ )
79
+ } catch (err) {
80
+ if (throwOnError) throw err
81
+ console.log('fetchGasLimit error', err)
82
+ }
83
+
84
+ return _isToken ? asset.gasLimit : asset.contractGasLimit
85
+ }
86
+
87
+ function normalizeAmount(amount) {
88
+ if (!Buffer.isBuffer(amount)) {
89
+ amount = currency2buffer(amount)
90
+ }
91
+
92
+ amount = ethUtil.bufferToHex(amount)
93
+ while (amount[2] === '0') amount = '0x' + amount.slice(3)
94
+ if (amount === '0x') amount = '0x0'
95
+
96
+ return amount
97
+ }
98
+
99
+ function normalizeGasPrice(gasPrice) {
100
+ while (gasPrice[2] === '0') gasPrice = '0x' + gasPrice.slice(3)
101
+ if (gasPrice === '0x') gasPrice = '0x0'
102
+ return gasPrice
103
+ }
@@ -0,0 +1,38 @@
1
+ import { isRpcBalanceAsset } from '@exodus/ethereum-lib'
2
+
3
+ const fixBalance = ({ txLog, balance }) => {
4
+ for (const tx of txLog) {
5
+ // TODO: pending can only be less than a few minutes old, we can only search the latest txs to improve performance
6
+ if (tx.sent && tx.pending && !tx.error) {
7
+ // coinAmount is negative for sent tx
8
+ balance = balance.sub(tx.coinAmount.abs())
9
+ if (tx.coinAmount.unitType.equals(tx.feeAmount.unitType)) {
10
+ balance = balance.sub(tx.feeAmount)
11
+ }
12
+ }
13
+ }
14
+ return balance
15
+ }
16
+
17
+ /**
18
+ * Api method to return the balance based on either account state balances or tx history.
19
+ *
20
+ * @param asset the asset to get the balances
21
+ * @param txLog the txLog when the balance is transaction based
22
+ * @param accountState the account state when the balance is loaded from RPC
23
+ * @returns {{balance}|null} an object with the balance or null if the balance is unknown/zero
24
+ */
25
+ export const getBalances = ({ asset, txLog, accountState }) => {
26
+ if (isRpcBalanceAsset(asset)) {
27
+ const balance =
28
+ asset.baseAsset.name === asset.name
29
+ ? accountState?.balance
30
+ : accountState?.tokenBalances?.[asset.name]
31
+ return balance && !balance.isZero ? { balance: fixBalance({ txLog, balance }) } : null
32
+ }
33
+ return txLog.size
34
+ ? {
35
+ balance: txLog.getMutations().slice(-1)[0].balance,
36
+ }
37
+ : null
38
+ }
@@ -0,0 +1,11 @@
1
+ // Ideally, we should reduce the exports to just api.
2
+ // If the client is importing one module specifically,
3
+ // it's breaking the encapsulation
4
+ export * from './eth-like-util'
5
+ export * from './fee-monitor'
6
+ export * from './gas-estimation'
7
+ export * from './exodus-eth-server'
8
+ export * from './tx-log'
9
+ export * from './get-balances'
10
+ export * from './staking'
11
+ export * from './simulate-tx'
@@ -0,0 +1,21 @@
1
+ const ETHEREUM_TX_PREVIEW_API = 'https://simulation.a.exodus.io/simulate'
2
+
3
+ export async function fetchTxPreview(transaction) {
4
+ const response = await fetch(ETHEREUM_TX_PREVIEW_API, {
5
+ method: 'POST',
6
+ headers: {
7
+ 'Content-Type': 'application/json',
8
+ },
9
+ body: JSON.stringify({
10
+ network: 'ethereum',
11
+ chain: 'main',
12
+ transactions: [transaction],
13
+ }),
14
+ })
15
+
16
+ if (!response.ok) {
17
+ throw new Error(await response.text())
18
+ }
19
+
20
+ return response.json()
21
+ }
@@ -0,0 +1,2 @@
1
+ export * from './fetch-tx-preview'
2
+ export * from './simulate-eth-tx'
@@ -0,0 +1,86 @@
1
+ import assets from '@exodus/assets'
2
+
3
+ import { fetchTxPreview } from './fetch-tx-preview'
4
+ import { getERC20Params } from '../eth-like-util'
5
+
6
+ const ethDecimals = assets.ethereum.units.ETH
7
+
8
+ const ethHexToInt = (hexValue) => parseInt(hexValue, '16')
9
+
10
+ async function getDecimals(type, assetContractAddress = null) {
11
+ if (type === 'erc20' && assetContractAddress) {
12
+ const { decimals } = await getERC20Params({
13
+ address: assetContractAddress,
14
+ assetName: 'ethereum',
15
+ paramNames: ['decimals'],
16
+ })
17
+
18
+ return decimals
19
+ }
20
+
21
+ return ethDecimals
22
+ }
23
+
24
+ export async function simulateAndRetrieveSideEffects(transaction) {
25
+ const willSend = []
26
+ const willReceive = []
27
+
28
+ if (!transaction.to) throw new Error(`'to' field is missing in the TX object`)
29
+
30
+ const blocknativeTxObject = {
31
+ to: transaction.to,
32
+ input: transaction.data,
33
+ from: transaction.from,
34
+ value: ethHexToInt(transaction.value),
35
+ gas: ethHexToInt(transaction.gas),
36
+ maxFeePerGas: ethHexToInt(transaction.maxFeePerGas),
37
+ maxPriorityFeePerGas: ethHexToInt(transaction.maxPriorityFeePerGas),
38
+ }
39
+
40
+ try {
41
+ const simulatedTx = await fetchTxPreview(blocknativeTxObject)
42
+
43
+ const [simulatedBalanceChanges] = simulatedTx.netBalanceChanges
44
+
45
+ const [sender] =
46
+ simulatedBalanceChanges &&
47
+ simulatedBalanceChanges.filter(({ address }) => address === transaction.from)
48
+
49
+ if (sender) {
50
+ for (const balanceChange of sender.balanceChanges) {
51
+ const { delta, asset } = balanceChange
52
+
53
+ const decimal = await getDecimals(asset.type, asset.contractAddress)
54
+
55
+ if (delta.startsWith('-')) {
56
+ willSend.push({
57
+ symbol: asset.symbol,
58
+ balance: delta.slice(1),
59
+ assetType: asset.type,
60
+ decimal,
61
+ })
62
+ } else {
63
+ willReceive.push({
64
+ symbol: asset.symbol,
65
+ balance: delta,
66
+ assetType: asset.type,
67
+ decimal,
68
+ })
69
+ }
70
+ }
71
+ }
72
+
73
+ if (!willSend.length) {
74
+ willSend.push({
75
+ symbol: 'ETH',
76
+ balance: ethHexToInt(transaction.value).toString(),
77
+ assetType: 'ether',
78
+ decimal: ethDecimals,
79
+ })
80
+ }
81
+ } catch (_) {
82
+ throw new Error('Simulation of Ethereum transaction failed.')
83
+ }
84
+
85
+ return { willSend, willReceive }
86
+ }
@@ -0,0 +1,115 @@
1
+ import { createContract } from '@exodus/ethereum-lib'
2
+
3
+ export const SFC_ADDRESS = '0xfc00face00000000000000000000000000000000'
4
+
5
+ export class FantomStaking {
6
+ constructor() {
7
+ this.sfcContract = createContract(SFC_ADDRESS, 'fantomSfc')
8
+ }
9
+
10
+ buildTxData = (contract, method, ...args) => {
11
+ return contract[method].build(...args)
12
+ }
13
+
14
+ /**
15
+ * Get tx data of method to delegate (stake) amount to validator
16
+ */
17
+ delegate = ({ validatorId }) => {
18
+ this.#validateValidatorId(validatorId)
19
+ return this.buildTxData(this.sfcContract, 'delegate', validatorId)
20
+ }
21
+
22
+ /**
23
+ * undelegate creates a transaction preparing part of the delegations
24
+ * to be withdrawn.
25
+ *
26
+ * Note: The amount must be lower than the total delegation amount for that account. Also,
27
+ * the requestId value has to be unique and previously unused numeric identifier of the new
28
+ * withdrawal. The actual withdraw execution, available after a lock period, will use the same
29
+ * request id to process the prepared withdrawal.
30
+ */
31
+ undelegate = ({ requestId, validatorId, amount }) => {
32
+ this.#validateRequestId(requestId)
33
+ this.#validateValidatorId(validatorId)
34
+ return this.buildTxData(this.sfcContract, 'undelegate', validatorId, requestId, amount)
35
+ }
36
+
37
+ /**
38
+ * withdraw creates a transaction executing partial withdraw for the given
39
+ * prepared withdraw request.
40
+ *
41
+ * Note: The request id has to exist and has to be prepared for the withdraw to execute
42
+ * correctly.
43
+ */
44
+ withdraw = ({ requestId, validatorId }) => {
45
+ this.#validateRequestId(requestId)
46
+ this.#validateValidatorId(validatorId)
47
+ return this.buildTxData(this.sfcContract, 'withdraw', validatorId, requestId)
48
+ }
49
+
50
+ /**
51
+ * claimRewards creates a new delegator rewards claiming transaction.
52
+ * The call transfers all the rewards from SFC back to the stake in single transaction.
53
+ */
54
+ claimRewards = ({ validatorId }) => {
55
+ this.#validateValidatorId(validatorId)
56
+ return this.buildTxData(this.sfcContract, 'claimRewards', validatorId)
57
+ }
58
+
59
+ /**
60
+ * restakeRewards creates a new delegator rewards claiming transaction.
61
+ * The call transfers all the rewards from SFC back to the stake in single transaction.
62
+ */
63
+ restakeRewards = ({ validatorId }) => {
64
+ this.#validateValidatorId(validatorId)
65
+ return this.buildTxData(this.sfcContract, 'restakeRewards', validatorId)
66
+ }
67
+
68
+ /**
69
+ * lockStake creates a transaction for locking delegation.
70
+ */
71
+ lockStake = ({ validatorId, amount, durationSeconds }) => {
72
+ this.#validateValidatorId(validatorId)
73
+ this.#validateDuration(durationSeconds)
74
+ return this.buildTxData(this.sfcContract, 'lockStake', validatorId, durationSeconds, amount)
75
+ }
76
+
77
+ /**
78
+ * relockStake creates a transaction for re-locking delegation.
79
+ */
80
+ relockStake = ({ validatorId, amount, durationSeconds }) => {
81
+ this.#validateValidatorId(validatorId)
82
+ this.#validateDuration(durationSeconds)
83
+ return this.buildTxData(this.sfcContract, 'relockStake', validatorId, durationSeconds, amount)
84
+ }
85
+
86
+ /**
87
+ * unlockStake creates a transaction for unlocking delegation.
88
+ */
89
+ unlockStake = ({ validatorId, amount }) => {
90
+ this.#validateValidatorId(validatorId)
91
+ return this.buildTxData(this.sfcContract, 'unlockStake', validatorId, amount)
92
+ }
93
+
94
+ #validateValidatorId = (validatorId) => {
95
+ if (!Number.isInteger(validatorId) || validatorId <= 0) {
96
+ throw new Error('Validator id must be positive unsigned integer value.')
97
+ }
98
+ }
99
+
100
+ #validateRequestId = (requestId) => {
101
+ if (!Number.isInteger(requestId) || requestId <= 0) {
102
+ throw new Error('Request id must be a valid numeric identifier.')
103
+ }
104
+ }
105
+
106
+ #validateDuration = (duration) => {
107
+ if (!Number.isInteger(duration)) {
108
+ throw new Error('The lock duration must be an integer.')
109
+ }
110
+
111
+ if (duration > 365 * 86400) {
112
+ throw new Error('The lock duration must be at most 365 days.')
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,2 @@
1
+ export { MaticStaking } from './matic-staking'
2
+ export { FantomStaking } from './fantom-staking'