• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GoodDollar / GoodServer / 17411668733

01 Sep 2025 09:03AM UTC coverage: 49.555%. Remained the same
17411668733

push

github

web-flow
Xdc update (#503)

* add: xdc wallet support

* add: make verifyIdentifier uses correct chainid to support smartwallets

* add: xdc bridge fees, small chains refactoring

* fix: missing network configs

---------

Co-authored-by: LewisB <lewis@gooddollar.org>

617 of 1518 branches covered (40.65%)

Branch coverage included in aggregate %.

22 of 51 new or added lines in 7 files covered. (43.14%)

392 existing lines in 5 files now uncovered.

1888 of 3537 relevant lines covered (53.38%)

7.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

24.71
/src/server/blockchain/MultiWallet.js
1
// @flow
2
import { assign, every, forOwn, isEmpty, isError, map, some } from 'lodash'
3
import AdminWallet from './AdminWallet'
4
import { CeloAdminWallet } from './CeloAdminWallet'
5
import { BaseAdminWallet } from './BaseAdminWallet'
6
import { XdcAdminWallet } from './XdcAdminWallet'
7

8
import conf from '../server.config'
9
import logger from '../../imports/logger'
10
import { DefenderRelayer } from './DefenderRelayer'
11
const multiLogger = logger.child({ from: 'MultiWallet' })
5✔
12

13
class MultiWallet {
14
  mainWallet = null
5✔
15
  otherWallets = []
5✔
16
  wallets = []
5✔
17
  walletsMap = {}
5✔
18
  defaultChainId = null
5✔
19
  signer: DefenderRelayer = null
5✔
20

21
  get ready() {
22
    return Promise.all(map(this.wallets, 'ready')).then(() => this.mainWallet.addresses)
3✔
23
  }
24

25
  constructor(walletsMap) {
26
    let mainWallet
27
    let defaultChainId
28
    this.signer = DefenderRelayer.getInstance()
5✔
29

30
    forOwn(walletsMap, (wallet, chainId) => {
5✔
31
      this.wallets.push(wallet)
5✔
32

33
      if (mainWallet) {
5!
UNCOV
34
        this.otherWallets.push(wallet)
×
35
      } else {
36
        mainWallet = wallet
5✔
37
        defaultChainId = chainId
5✔
38
      }
39
    })
40

41
    multiLogger.debug('MultiWallet constructor:', {
5✔
42
      wallets: Object.keys(walletsMap),
43
      mainWallet: mainWallet.networkId,
UNCOV
44
      otherWallets: this.otherWallets.map(_ => _.networkId)
×
45
    })
46

47
    assign(this, { walletsMap, mainWallet, defaultChainId })
5✔
48
  }
49

50
  signMessage(message: string) {
UNCOV
51
    return this.signer.signMessage(message)
×
52
  }
53

54
  async banInFaucet(account, chainId = 'all', log = multiLogger) {
×
UNCOV
55
    const runTx = wallet => wallet.banInFaucet(account, log)
×
56

UNCOV
57
    log.debug('MultiWallet: banInFaucet request:', { account, chainId })
×
58
    if (chainId === 'all') {
×
59
      // ban on chains that have ubi
UNCOV
60
      const results = await Promise.all(
×
61
        this.wallets.filter(_ => Number(_.UBIContract?._address) > 0).map(wallet => runTx(wallet).catch(e => e))
×
62
      )
UNCOV
63
      const error = results.find(isError)
×
64

UNCOV
65
      if (error) {
×
66
        throw error
×
67
      }
68

UNCOV
69
      return results
×
70
    }
71

UNCOV
72
    const { walletsMap } = this
×
73

UNCOV
74
    if (chainId in walletsMap) return runTx(walletsMap[chainId])
×
75

UNCOV
76
    throw new Error(`unsupported chain ${chainId}`)
×
77
  }
78

79
  async topWallet(account, chainId = null, log = multiLogger) {
×
UNCOV
80
    const runTx = wallet => wallet.topWallet(account, log)
×
81

82
    log.debug('MultiWallet: topWallet request:', { account, chainId })
83
    if (chainId === 'all') {
UNCOV
84
      // topwallet on chains that have ubi
×
85
      const results = await Promise.all(
86
        this.wallets.filter(_ => Number(_.UBIContract?._address) > 0).map(wallet => runTx(wallet).catch(e => e))
87
      )
UNCOV
88
      const error = results.find(isError)
×
89

×
90
      if (error) {
91
        throw error
92
      }
93

UNCOV
94
      return results
×
95
    }
96

97
    const { walletsMap } = this
UNCOV
98

×
99
    if (chainId in walletsMap) return runTx(walletsMap[chainId])
100

101
    throw new Error(`unsupported chain ${chainId}`)
×
UNCOV
102
  }
×
103

×
104
  async whitelistUser(account, did, chainId = null, log = multiLogger) {
UNCOV
105
    return Promise.all(this.wallets.map(wallet => wallet.whitelistUser(account, did, chainId, 0, log)))
×
106
  }
UNCOV
107

×
108
  async removeWhitelisted(account) {
×
109
    return Promise.all(this.wallets.map(wallet => wallet.removeWhitelisted(account)))
110
  }
UNCOV
111

×
112
  async verifiedStatus(account) {
UNCOV
113
    return Promise.all(
×
114
      this.wallets.map(wallet => wallet.isVerified(account).then(status => ({ chainId: wallet.networkId, status })))
×
115
    )
×
116
  }
UNCOV
117

×
118
  async isVerified(account) {
UNCOV
119
    return this.mainWallet.isVerified(account)
×
120
  }
UNCOV
121

×
122
  async isConnected(account) {
UNCOV
123
    // Base has no Identity contract; isConnected would fail there. Only run on wallets that have Identity.
×
124
    const walletsWithIdentity = this.wallets.filter(w => !(w instanceof BaseAdminWallet))
×
125
    const results = await Promise.all(walletsWithIdentity.map(wallet => wallet.isConnected(account)))
×
126
    return results.some(Boolean)
127
  }
UNCOV
128

×
129
  async lastAuthenticated(account) {
130
    return this.mainWallet.getLastAuthenticated(account)
131
  }
UNCOV
132

×
133
  async syncWhitelist(account, log = multiLogger) {
UNCOV
134
    try {
×
135
      const isVerified = await Promise.all(this.wallets.map(wallet => wallet.isVerified(account)))
×
136

137
      log.debug('syncwhitelist isVerified:', { account, isVerified })
138

139
      if (isEmpty(isVerified) || every(isVerified) || !some(isVerified)) {
UNCOV
140
        return false
×
141
      }
142

143
      const mainWallet = this.wallets[isVerified.findIndex(_ => _)]
×
144

1✔
145
      const [did, lastAuthenticated] = await Promise.all([
146
        mainWallet.getDID(account).catch(() => account),
147
        mainWallet.getLastAuthenticated(account).catch(() => 0)
148
      ])
5✔
149
      const chainId = mainWallet.networkId
5!
UNCOV
150

×
151
      log.debug('syncwhitelist did:', { account, did, lastAuthenticated, chainId })
×
152

153
      await Promise.all(
154
        isVerified.map(async (status, index) => {
155
          log.debug('syncwhitelist whitelisting on wallet:', { status, index, account })
156
          if (status) {
157
            return
158
          }
159

160
          await this.wallets[index].whitelistUser(account, did, chainId, lastAuthenticated, log)
161
        })
162
      )
163

164
      return true
165
    } catch (e) {
166
      log.error('syncwhitelist failed:', e.message, e, { account })
167
      throw e
168
    }
169
  }
170

171
  async getAuthenticationPeriod() {
172
    return this.mainWallet.getAuthenticationPeriod()
173
  }
174

175
  async registerRedtent(account: string, countryCode: string, log = multiLogger) {
176
    return Promise.all(this.wallets.map(wallet => wallet.registerRedtent(account, countryCode, log)))
177
  }
178
}
179

180
let otherWallets = {}
181
if (conf.env !== 'test') {
182
  const celoWallet = new CeloAdminWallet()
183
  otherWallets = {
184
    42220: celoWallet,
185
    8453: new BaseAdminWallet({}, celoWallet),
186
    50: new XdcAdminWallet({})
187
  }
188
}
189

190
export default new MultiWallet({
191
  122: AdminWallet, // "main" wallet goes first
192
  ...otherWallets
193
})
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc