decred.org/dcrdex@v1.0.5/client/webserver/site/src/js/order.ts (about)

     1  import Doc from './doc'
     2  import BasePage from './basepage'
     3  import * as OrderUtil from './orderutil'
     4  import { bind as bindForm, AccelerateOrderForm } from './forms'
     5  import { postJSON } from './http'
     6  import * as intl from './locales'
     7  import {
     8    app,
     9    Order,
    10    PageElement,
    11    OrderNote,
    12    MatchNote,
    13    Match,
    14    Coin
    15  } from './registry'
    16  import { setOptionTemplates } from './opts'
    17  import { formatCoinID, setCoinHref } from './coinexplorers'
    18  
    19  // lockTimeMakerMs must match the value returned from LockTimeMaker func in
    20  // bisonw.
    21  const lockTimeMakerMs = 20 * 60 * 60 * 1000
    22  // lockTimeTakerMs must match the value returned from LockTimeTaker func in
    23  // bisonw.
    24  const lockTimeTakerMs = 8 * 60 * 60 * 1000
    25  
    26  const animationLength = 500
    27  
    28  export default class OrderPage extends BasePage {
    29    orderID: string
    30    order: Order
    31    page: Record<string, PageElement>
    32    currentForm: HTMLElement
    33    secondTicker: number
    34    refreshOnPopupClose: boolean
    35    accelerateOrderForm: AccelerateOrderForm
    36    stampers: PageElement[]
    37  
    38    constructor (main: HTMLElement) {
    39      super()
    40      const page = this.page = Doc.idDescendants(main)
    41      this.stampers = Doc.applySelector(main, '[data-stamp]')
    42      // Find the order
    43      this.orderID = main.dataset.oid || ''
    44  
    45      Doc.cleanTemplates(page.matchCardTmpl)
    46  
    47      const setStamp = () => {
    48        for (const span of this.stampers) {
    49          span.textContent = Doc.timeSince(parseInt(span.dataset.stamp || ''))
    50        }
    51      }
    52      setStamp()
    53  
    54      page.forms.querySelectorAll('.form-closer').forEach(el => {
    55        Doc.bind(el, 'click', () => {
    56          if (this.refreshOnPopupClose) {
    57            window.location.replace(window.location.href)
    58            return
    59          }
    60          Doc.hide(page.forms)
    61        })
    62      })
    63  
    64      // Some static elements on this page contain assets that can be linked
    65      // to blockchain explorers (such as Etherscan) so users can easily
    66      // examine funding/acceleration coins data there. We'd need to set up
    67      // such hyperlinks here.
    68      main.querySelectorAll('[data-explorer-id]').forEach((link: PageElement) => {
    69        const assetID = parseInt(link.dataset.explorerId || '')
    70        setCoinHref(assetID, link)
    71      })
    72  
    73      if (page.cancelBttn) {
    74        Doc.bind(page.cancelBttn, 'click', () => {
    75          this.showForm(page.cancelForm)
    76        })
    77      }
    78  
    79      Doc.bind(page.accelerateBttn, 'click', () => {
    80        this.showAccelerateForm()
    81      })
    82  
    83      const success = () => {
    84        this.refreshOnPopupClose = true
    85      }
    86      // Do not call cleanTemplates before creating the AccelerateOrderForm
    87      setOptionTemplates(page)
    88      this.accelerateOrderForm = new AccelerateOrderForm(page.accelerateForm, success)
    89      Doc.cleanTemplates(page.booleanOptTmpl, page.rangeOptTmpl, page.orderOptTmpl)
    90  
    91      // If the user clicks outside of a form, it should close the page overlay.
    92      Doc.bind(page.forms, 'mousedown', (e: MouseEvent) => {
    93        if (!Doc.mouseInElement(e, this.currentForm)) {
    94          if (this.refreshOnPopupClose) {
    95            window.location.reload()
    96            return
    97          }
    98          Doc.hide(page.forms)
    99        }
   100      })
   101  
   102      // Cancel order form
   103      bindForm(page.cancelForm, page.cancelSubmit, async () => { this.submitCancel() })
   104  
   105      this.secondTicker = window.setInterval(() => {
   106        setStamp()
   107      }, 10000) // update every 10 seconds
   108  
   109      app().registerNoteFeeder({
   110        order: (note: OrderNote) => { this.handleOrderNote(note) },
   111        match: (note: MatchNote) => { this.handleMatchNote(note) }
   112      })
   113  
   114      this.start()
   115    }
   116  
   117    async start () {
   118      let ord = app().order(this.orderID)
   119      // app().order can only access active orders. If the order is not active,
   120      // we'll need to get the data from the database.
   121      if (ord) this.order = ord
   122      else {
   123        ord = await this.fetchOrder()
   124      }
   125      // Swap out the dot-notation symbols with token-aware symbols.
   126      this.page.mktBaseSymbol.replaceWith(Doc.symbolize(app().assets[ord.baseID]))
   127      this.page.mktQuoteSymbol.replaceWith(Doc.symbolize(app().assets[ord.quoteID]))
   128  
   129      this.setAccelerationButtonVis()
   130      this.showMatchCards()
   131    }
   132  
   133    unload () {
   134      clearInterval(this.secondTicker)
   135    }
   136  
   137    /* fetchOrder fetches the order from the client. */
   138    async fetchOrder (): Promise<Order> {
   139      const res = await postJSON('/api/order', this.orderID)
   140      if (!app().checkResponse(res)) throw res.msg
   141      this.order = res.order
   142      return this.order
   143    }
   144  
   145    /*
   146     * setImmutableMatchCardElements sets the match card elements that are never
   147     * changed.
   148     */
   149    setImmutableMatchCardElements (matchCard: HTMLElement, match: Match) {
   150      const tmpl = Doc.parseTemplate(matchCard)
   151  
   152      tmpl.matchID.textContent = match.matchID
   153  
   154      const time = new Date(match.stamp)
   155      tmpl.matchTime.textContent = time.toLocaleTimeString(Doc.languages(), {
   156        year: 'numeric',
   157        month: 'short',
   158        day: 'numeric'
   159      })
   160  
   161      tmpl.matchTimeAgo.dataset.stamp = match.stamp.toString()
   162      tmpl.matchTimeAgo.textContent = Doc.timeSince(match.stamp)
   163      this.stampers.push(tmpl.matchTimeAgo)
   164  
   165      const orderPortion = OrderUtil.orderPortion(this.order, match)
   166      const baseSymbol = Doc.bipSymbol(this.order.baseID)
   167      const quoteSymbol = Doc.bipSymbol(this.order.quoteID)
   168      const baseUnitInfo = app().unitInfo(this.order.baseID)
   169      const quoteUnitInfo = app().unitInfo(this.order.quoteID)
   170      const [bUnit, qUnit] = [baseUnitInfo.conventional.unit.toLowerCase(), quoteUnitInfo.conventional.unit.toLowerCase()]
   171      const quoteAmount = OrderUtil.baseToQuote(match.rate, match.qty)
   172  
   173      if (match.isCancel) {
   174        Doc.show(tmpl.cancelInfoDiv)
   175        Doc.hide(tmpl.infoDiv, tmpl.status, tmpl.statusHdr)
   176  
   177        if (this.order.sell) {
   178          tmpl.cancelAmount.textContent = Doc.formatCoinValue(match.qty, baseUnitInfo)
   179          tmpl.cancelIcon.src = Doc.logoPathFromID(this.order.baseID)
   180        } else {
   181          tmpl.cancelAmount.textContent = Doc.formatCoinValue(quoteAmount, quoteUnitInfo)
   182          tmpl.cancelIcon.src = Doc.logoPathFromID(this.order.quoteID)
   183        }
   184  
   185        tmpl.cancelOrderPortion.textContent = orderPortion
   186  
   187        return
   188      }
   189  
   190      Doc.show(tmpl.infoDiv)
   191      Doc.hide(tmpl.cancelInfoDiv)
   192  
   193      tmpl.orderPortion.textContent = orderPortion
   194  
   195      if (match.side === OrderUtil.Maker) {
   196        tmpl.side.textContent = intl.prep(intl.ID_MAKER)
   197        Doc.show(
   198          tmpl.makerSwapYou,
   199          tmpl.makerRedeemYou,
   200          tmpl.takerSwapThem,
   201          tmpl.takerRedeemThem
   202        )
   203        Doc.hide(
   204          tmpl.takerSwapYou,
   205          tmpl.takerRedeemYou,
   206          tmpl.makerSwapThem,
   207          tmpl.makerRedeemThem
   208        )
   209      } else {
   210        tmpl.side.textContent = intl.prep(intl.ID_TAKER)
   211        Doc.hide(
   212          tmpl.makerSwapYou,
   213          tmpl.makerRedeemYou,
   214          tmpl.takerSwapThem,
   215          tmpl.takerRedeemThem
   216        )
   217        Doc.show(
   218          tmpl.takerSwapYou,
   219          tmpl.takerRedeemYou,
   220          tmpl.makerSwapThem,
   221          tmpl.makerRedeemThem
   222        )
   223      }
   224  
   225      if ((match.side === OrderUtil.Maker && this.order.sell) ||
   226            (match.side === OrderUtil.Taker && !this.order.sell)) {
   227        tmpl.makerSwapAsset.textContent = bUnit
   228        tmpl.takerSwapAsset.textContent = qUnit
   229        tmpl.makerRedeemAsset.textContent = qUnit
   230        tmpl.takerRedeemAsset.textContent = bUnit
   231      } else {
   232        tmpl.makerSwapAsset.textContent = qUnit
   233        tmpl.takerSwapAsset.textContent = bUnit
   234        tmpl.makerRedeemAsset.textContent = bUnit
   235        tmpl.takerRedeemAsset.textContent = qUnit
   236      }
   237  
   238      const rate = app().conventionalRate(this.order.baseID, this.order.quoteID, match.rate)
   239      tmpl.rate.textContent = `${rate} ${bUnit}/${qUnit}`
   240  
   241      if (this.order.sell) {
   242        tmpl.refundAsset.textContent = baseSymbol
   243        tmpl.fromAmount.textContent = Doc.formatCoinValue(match.qty, baseUnitInfo)
   244        tmpl.toAmount.textContent = Doc.formatCoinValue(quoteAmount, quoteUnitInfo)
   245        tmpl.fromIcon.src = Doc.logoPathFromID(this.order.baseID)
   246        tmpl.toIcon.src = Doc.logoPathFromID(this.order.quoteID)
   247      } else {
   248        tmpl.refundAsset.textContent = quoteSymbol
   249        tmpl.fromAmount.textContent = Doc.formatCoinValue(quoteAmount, quoteUnitInfo)
   250        tmpl.toAmount.textContent = Doc.formatCoinValue(match.qty, baseUnitInfo)
   251        tmpl.fromIcon.src = Doc.logoPathFromID(this.order.quoteID)
   252        tmpl.toIcon.src = Doc.logoPathFromID(this.order.baseID)
   253      }
   254    }
   255  
   256    /*
   257     * setMutableMatchCardElements sets the match card elements which may get
   258     * updated on each update to the match.
   259     */
   260    setMutableMatchCardElements (matchCard: HTMLElement, m: Match) {
   261      if (m.isCancel) return
   262  
   263      const tmpl = Doc.parseTemplate(matchCard)
   264      tmpl.status.textContent = OrderUtil.matchStatusString(m)
   265  
   266      const tryShowCoin = (pendingEl: PageElement, coinLink: PageElement, coin: Coin) => {
   267        if (!coin) {
   268          Doc.hide(coinLink)
   269          Doc.show(pendingEl)
   270          return
   271        }
   272        coinLink.textContent = formatCoinID(coin.stringID)
   273        coinLink.dataset.explorerCoin = coin.stringID
   274        setCoinHref(coin.assetID, coinLink)
   275        Doc.show(coinLink)
   276        Doc.hide(pendingEl)
   277      }
   278  
   279      tryShowCoin(tmpl.makerSwapPending, tmpl.makerSwapCoin, makerSwapCoin(m))
   280      tryShowCoin(tmpl.takerSwapPending, tmpl.takerSwapCoin, takerSwapCoin(m))
   281      tryShowCoin(tmpl.makerRedeemPending, tmpl.makerRedeemCoin, makerRedeemCoin(m))
   282      tryShowCoin(tmpl.takerRedeemPending, tmpl.takerRedeemCoin, takerRedeemCoin(m))
   283      if (!m.refund) {
   284        // Special messaging for pending refunds.
   285        let lockTime = lockTimeMakerMs
   286        if (m.side === OrderUtil.Taker) lockTime = lockTimeTakerMs
   287        const refundAfter = new Date(m.stamp + lockTime)
   288        if (Date.now() > refundAfter.getTime()) tmpl.refundPending.textContent = intl.prep(intl.ID_REFUND_IMMINENT)
   289        else {
   290          const refundAfterStr = refundAfter.toLocaleTimeString(Doc.languages(), {
   291            year: 'numeric',
   292            month: 'short',
   293            day: 'numeric'
   294          })
   295          tmpl.refundPending.textContent = intl.prep(intl.ID_REFUND_WILL_HAPPEN_AFTER, { refundAfterTime: refundAfterStr })
   296        }
   297        Doc.hide(tmpl.refundCoin)
   298        Doc.show(tmpl.refundPending)
   299      } else {
   300        tmpl.refundCoin.textContent = formatCoinID(m.refund.stringID)
   301        tmpl.refundCoin.dataset.explorerCoin = m.refund.stringID
   302        setCoinHref(m.refund.assetID, tmpl.refundCoin)
   303        Doc.show(tmpl.refundCoin)
   304        Doc.hide(tmpl.refundPending)
   305      }
   306  
   307      if (m.status === OrderUtil.MakerSwapCast && !m.revoked && !m.refund) {
   308        const c = makerSwapCoin(m)
   309        tmpl.makerSwapMsg.textContent = confirmationString(c)
   310        Doc.hide(tmpl.takerSwapMsg, tmpl.makerRedeemMsg, tmpl.takerRedeemMsg)
   311        Doc.show(tmpl.makerSwapMsg)
   312      } else if (m.status === OrderUtil.TakerSwapCast && !m.revoked && !m.refund) {
   313        const c = takerSwapCoin(m)
   314        tmpl.takerSwapMsg.textContent = confirmationString(c)
   315        Doc.hide(tmpl.makerSwapMsg, tmpl.makerRedeemMsg, tmpl.takerRedeemMsg)
   316        Doc.show(tmpl.takerSwapMsg)
   317      } else if (inConfirmingMakerRedeem(m) && !m.revoked && !m.refund) {
   318        tmpl.makerRedeemMsg.textContent = confirmationString(m.redeem)
   319        Doc.hide(tmpl.makerSwapMsg, tmpl.takerSwapMsg, tmpl.takerRedeemMsg)
   320        Doc.show(tmpl.makerRedeemMsg)
   321      } else if (inConfirmingTakerRedeem(m) && !m.revoked && !m.refund) {
   322        tmpl.takerRedeemMsg.textContent = confirmationString(m.redeem)
   323        Doc.hide(tmpl.makerSwapMsg, tmpl.takerSwapMsg, tmpl.makerRedeemMsg)
   324        Doc.show(tmpl.takerRedeemMsg)
   325      } else {
   326        Doc.hide(tmpl.makerSwapMsg, tmpl.takerSwapMsg, tmpl.makerRedeemMsg, tmpl.takerRedeemMsg)
   327      }
   328  
   329      if (!m.revoked) {
   330        // Match is still following the usual success-path, it is desirable for the
   331        // user to see it in full (even if to learn how atomic swap is supposed to
   332        // work).
   333  
   334        Doc.setVis(makerSwapCoin(m) || m.active, tmpl.makerSwap)
   335        Doc.setVis(takerSwapCoin(m) || m.active, tmpl.takerSwap)
   336        Doc.setVis(makerRedeemCoin(m) || m.active, tmpl.makerRedeem)
   337        // When maker isn't aware of taker redeem coin, once the match becomes inactive
   338        // (nothing else maker is expected to do in this match) just hide taker redeem.
   339        Doc.setVis(takerRedeemCoin(m) || m.active, tmpl.takerRedeem)
   340        // Refunding isn't a usual part of success-path, but don't rule it out.
   341        Doc.setVis(m.refund, tmpl.refund)
   342      } else {
   343        // Match diverged from the usual success-path, since this could have happened
   344        // at any step it is hard (maybe impossible) to predict the final state this
   345        // match will end up in, so show only steps that already happened plus all
   346        // the possibilities on the next step ahead.
   347  
   348        // If we don't have swap coins after revocation, we won't show the pending message.
   349        Doc.setVis(makerSwapCoin(m), tmpl.makerSwap)
   350        Doc.setVis(takerSwapCoin(m), tmpl.takerSwap)
   351        const takerRefundsAfter = new Date(m.stamp + lockTimeTakerMs)
   352        const takerLockTimeExpired = Date.now() > takerRefundsAfter.getTime()
   353        // When match is revoked and both swaps are present, maker redeem might still show up:
   354        // - as maker, we'll try to redeem until taker locktime expires (if taker refunds
   355        //   we won't be able to redeem; even if taker hasn't refunded just yet - it
   356        //   becomes too dangerous to redeem after taker locktime expired because maker
   357        //   reveals his secret when redeeming, and taker might be able to submit both
   358        //   redeem and refund transactions before maker's redeem gets mined), so we'll
   359        //   have to show redeem pending element until maker redeem shows up, or until
   360        //   we give up on redeeming due to taker locktime expiry.
   361        // - as taker, we should expect maker redeeming any time, so we'll have to show
   362        //   redeem pending element until maker redeem shows up, or until we refund.
   363        Doc.setVis(makerRedeemCoin(m) || (takerSwapCoin(m) && m.active && !m.refund && !takerLockTimeExpired), tmpl.makerRedeem)
   364        // When maker isn't aware of taker redeem coin, once the match becomes inactive
   365        // (nothing else maker is expected to do in this match) just hide taker redeem.
   366        Doc.setVis(takerRedeemCoin(m) || (makerRedeemCoin(m) && m.active && !m.refund), tmpl.takerRedeem)
   367        // As taker, show refund placeholder only if we have outstanding swap to refund.
   368        // There is no need to wait for anything else, we can show refund placeholder
   369        // (to inform the user that it is likely to happen) right after match revocation.
   370        let expectingRefund = Boolean(takerSwapCoin(m)) // as taker
   371        if (m.side === OrderUtil.Maker) {
   372          // As maker, show refund placeholder only if we have outstanding swap to refund.
   373          // If we don't have taker swap there is no need to wait for anything else, we
   374          // can show refund placeholder (to inform the user that it is likely to happen)
   375          // right after match revocation.
   376          expectingRefund = Boolean(makerSwapCoin(m))
   377          // If we discover taker swap we'll be trying to redeem it (instead of trying
   378          // to refund our own swap) until taker refunds, so start showing refund
   379          // placeholder only after taker is expected to start his refund process in
   380          // this case.
   381          if (takerSwapCoin(m)) {
   382            expectingRefund = expectingRefund && takerLockTimeExpired
   383          }
   384        }
   385        Doc.setVis(m.refund || (m.active && !m.redeem && !m.counterRedeem && expectingRefund), tmpl.refund)
   386      }
   387    }
   388  
   389    /*
   390     * addNewMatchCard adds a new card to the list of match cards.
   391     */
   392    addNewMatchCard (match: Match) {
   393      const page = this.page
   394      const matchCard = page.matchCardTmpl.cloneNode(true) as HTMLElement
   395      app().bindUrlHandlers(matchCard)
   396      matchCard.dataset.matchID = match.matchID
   397      this.setImmutableMatchCardElements(matchCard, match)
   398      this.setMutableMatchCardElements(matchCard, match)
   399      page.matchBox.appendChild(matchCard)
   400    }
   401  
   402    /*
   403     * showMatchCards creates cards for each match in the order.
   404     */
   405    showMatchCards () {
   406      const order = this.order
   407      if (!order) return
   408      if (!order.matches) return
   409      order.matches.sort((a, b) => a.stamp - b.stamp)
   410      order.matches.forEach((match) => this.addNewMatchCard(match))
   411    }
   412  
   413    /* showCancel shows a form to confirm submission of a cancel order. */
   414    showCancel () {
   415      const order = this.order
   416      const page = this.page
   417      const remaining = order.qty - order.filled
   418      const asset = OrderUtil.isMarketBuy(order) ? app().assets[order.quoteID] : app().assets[order.baseID]
   419      page.cancelRemain.textContent = Doc.formatCoinValue(remaining, asset.unitInfo)
   420      page.cancelUnit.textContent = asset.unitInfo.conventional.unit.toUpperCase()
   421      this.showForm(page.cancelForm)
   422    }
   423  
   424    /* showForm shows a modal form with a little animation. */
   425    async showForm (form: HTMLElement) {
   426      this.currentForm = form
   427      const page = this.page
   428      Doc.hide(page.cancelForm, page.accelerateForm)
   429      form.style.right = '10000px'
   430      Doc.show(page.forms, form)
   431      const shift = (page.forms.offsetWidth + form.offsetWidth) / 2
   432      await Doc.animate(animationLength, progress => {
   433        form.style.right = `${(1 - progress) * shift}px`
   434      }, 'easeOutHard')
   435      form.style.right = '0px'
   436    }
   437  
   438    /* submitCancel submits a cancellation for the order. */
   439    async submitCancel () {
   440      // this will be the page.cancelSubmit button (evt.currentTarget)
   441      const page = this.page
   442      const order = this.order
   443      const req = {
   444        orderID: order.id
   445      }
   446      const loaded = app().loading(page.cancelForm)
   447      const res = await postJSON('/api/cancel', req)
   448      loaded()
   449      if (!app().checkResponse(res)) return
   450      page.status.textContent = intl.prep(intl.ID_CANCELING)
   451      Doc.hide(page.forms)
   452      order.cancelling = true
   453    }
   454  
   455    /*
   456     * setAccelerationButtonVis shows the acceleration button if the order can
   457     * be accelerated.
   458     */
   459    setAccelerationButtonVis () {
   460      const order = this.order
   461      if (!order) return
   462      const page = this.page
   463      Doc.setVis(app().canAccelerateOrder(order), page.accelerateBttn, page.actionsLabel)
   464    }
   465  
   466    /* showAccelerateForm shows a form to accelerate an order */
   467    async showAccelerateForm () {
   468      const loaded = app().loading(this.page.accelerateBttn)
   469      this.accelerateOrderForm.refresh(this.order)
   470      loaded()
   471      this.showForm(this.page.accelerateForm)
   472    }
   473  
   474    /*
   475     * handleOrderNote is the handler for the 'order'-type notification, which are
   476     * used to update an order's status.
   477     */
   478    handleOrderNote (note: OrderNote) {
   479      const page = this.page
   480      const order = note.order
   481      if (order.id !== this.orderID) return
   482      this.order = order
   483      const bttn = page.cancelBttn
   484      if (bttn && order.status > OrderUtil.StatusBooked) Doc.hide(bttn)
   485      page.status.textContent = OrderUtil.statusString(order)
   486      for (const m of order.matches || []) this.processMatch(m)
   487      this.setAccelerationButtonVis()
   488    }
   489  
   490    /* handleMatchNote handles a 'match' notification. */
   491    handleMatchNote (note: MatchNote) {
   492      if (note.orderID !== this.orderID) return
   493      this.processMatch(note.match)
   494      this.setAccelerationButtonVis()
   495    }
   496  
   497    /*
   498     * processMatch synchronizes a match's card with a match received in a
   499     * 'order' or 'match' notification.
   500     */
   501    processMatch (m: Match) {
   502      let card: HTMLElement | null = null
   503      for (const div of Doc.applySelector(this.page.matchBox, '.match-card')) {
   504        if (div.dataset.matchID === m.matchID) {
   505          card = div
   506          break
   507        }
   508      }
   509      if (card) {
   510        this.setMutableMatchCardElements(card, m)
   511      } else {
   512        this.addNewMatchCard(m)
   513      }
   514    }
   515  }
   516  
   517  /*
   518   * confirmationString is a string describing the state of confirmations for a
   519   * coin.
   520   * */
   521  function confirmationString (coin: Coin) {
   522    if (!coin.confs || coin.confs.required === 0) return ''
   523    return `${coin.confs.count} / ${coin.confs.required} ${intl.prep(intl.ID_CONFIRMATIONS)}`
   524  }
   525  
   526  // makerSwapCoin return's the maker's swap coin.
   527  function makerSwapCoin (m: Match) : Coin {
   528    return (m.side === OrderUtil.Maker) ? m.swap : m.counterSwap
   529  }
   530  
   531  // takerSwapCoin return's the taker's swap coin.
   532  function takerSwapCoin (m: Match) {
   533    return (m.side === OrderUtil.Maker) ? m.counterSwap : m.swap
   534  }
   535  
   536  // makerRedeemCoin return's the maker's redeem coin.
   537  function makerRedeemCoin (m: Match) {
   538    return (m.side === OrderUtil.Maker) ? m.redeem : m.counterRedeem
   539  }
   540  
   541  // takerRedeemCoin return's the taker's redeem coin.
   542  function takerRedeemCoin (m: Match) {
   543    return (m.side === OrderUtil.Maker) ? m.counterRedeem : m.redeem
   544  }
   545  
   546  /*
   547  * inConfirmingMakerRedeem will be true if we are the maker, and we are waiting
   548  * on confirmations for our own redeem.
   549  */
   550  function inConfirmingMakerRedeem (m: Match) {
   551    return m.status < OrderUtil.MatchConfirmed && m.side === OrderUtil.Maker && m.status >= OrderUtil.MakerRedeemed
   552  }
   553  
   554  /*
   555  * inConfirmingTakerRedeem will be true if we are the taker, and we are waiting
   556  * on confirmations for our own redeem.
   557  */
   558  function inConfirmingTakerRedeem (m: Match) {
   559    return m.status < OrderUtil.MatchConfirmed && m.side === OrderUtil.Taker && m.status >= OrderUtil.MatchComplete
   560  }