Client

Best Practices

Configuration

The best way to get a new instance of the connext client is using the getConnextClient function, and supplying the HUB_URL and RPC_URL as environment variables. You should instantiate the web3 object you pass into the client with the rpcUrl from the .env, and directly supply the HUB_URL to the client options on start.

The client will autoconfigure the auth, and all the addresses and network information with the hub on start.

Making Payments

It is recommended that all client implementers read through the core concepts before implementing the client.

In general, if a payment is not fully collateralized, send an intentionally failing payment to trigger the hubs autocollateral mechanism and wait for the onchain transaction to be confirmed.

If you would rather avoid complexities around collateral completely, use a custodial payment instead.

Runtime Flags

When working with payment channels, there are certain precautions you have to take when you are updating the state to avoid breaking state with the hub and having to dispute your channel.

It is recommended that all implementers use the RuntimeState flags to restrict user actions at a UI level. Additionally, these flags can be used to display more descriptive details to users (i.e. communicate which phase an onchain user-deposit transaction is in).

Documentation

Methods:

State:

getConnextClient

getConnextClient(options: ConnextClientOptions): <ConnextClient>

Retrieve a new instance of the connext client initialized with the provided parameters.

Parameters:

Name Type Description
options ConnextClientOptions the options to initialize the client with

Returns: ConnextClient

Example:

const connext = getConnextClient({...})
// register listener
connext.on('onStateChange', state => {
  console.log('Connext state changed:', state);
})
await connext.start() // start polling

buy

buy(purchase: PurchaseRequest): Promise<object>

Make a purchase (group of related payments) within the channel. Purchases can have meta objects associated with them. These objects are stored as JSONs by the hub.

There are 5 primary types of payments you can make:

  • [PT_CHANNEL] - A payment between channel participants (to or from hub directly)

  • [PT_THREAD] - Noncustodial payments to others who have channels open with the hub. Users must be online to receive these payments. Threads can be thought of as unidirectional, short-lived channels.

  • [PT_CUSTODIAL] - Purely custodial payments through the hub.

  • [PT_LINK] - A payment generated by a secret created by the sender. This payment is only redeemable of the receiver can provide the secret, and is redeemable by anyone. This is a custodial payment.

All non-custodial payment types are subject to availablility and collateral requirements.

Parameters:

Name Type Description
purchase PurchaseRequest the PurchaseRequest sent to the hub.

Returns: Promise<{ purchaseId: string }>

Example:

// any information stored in the meta is stored
// by the hub and linked to the purchase made
const meta: MetadataType = {
  sku: "somePurchaseInfo"
}
const amount: Payment = { // in wei units
  wei: "1000",
  tokens: "0",
}
// a group of related payments, e.g. a tip and a fee
// this is the basic structure for each outlined type of payment
const payments: PurchasePaymentRequest[] = [
  {
    recipient: connext.opts.hubAddress,
    amount,
    type: "PT_CHANNEL"
  },
  {
    recipient: "0xsa3g...", // a user that has a channel with the hub
    amount,
    type: "PT_THREAD"
  },
  {
    recipient: "0xgf5s...", // a user who may be offline frequently
    amount,
    type: "PT_CUSTODIAL"
  },
  {
    recipient: emptyAddress, // use "0x000..."
    amount,
    type: "PT_LINK",
    meta: {
      secret: connext.generateSecret()
    }
  },
]

const { purchaseId } = await connext.buy({
  meta,
  payments,
})

It is important to note that while the metadata can be unstructured JSONs for any payments, if using PT_LINK type payments, you MUST include a generated secret in the meta on the payment, not the higher level purchase.


deposit

deposit(payment: Payment): Promise<void>

This method allows users to deposit into their channels.

It generates a ProposePendingDeposit state update, which is cosigned by the hub, and sent to chain by the user.

If there is a problem with the onchain transaction, the client will automatically send an Invalidation update to the hub and revert any pending deposits into their channel.

While users have an onchain transaction in flight, they are not able to make further updates to their state until the transaction is either confirmed onchain, or it is invalidated.

Parameters:

Name Type Description
payment Payment the deposit amount as a Payment(types.html#payment) type

Returns: Promise<void>

Example:

// send a token and eth deposit to the channel
await connext.deposit({
  amountWei: "10000",
  amountToken: "1000",
})
// the connext class is an event emitter. Implementers can
// subscribe to channel state changes via registered listeners

Note: since the user is sending the onchain transaction, there must be enough eth in the signing wallet to afford the gas of the deposit transaction * connext.opts.gasMultiple.


exchange

exchange(toSell: string, currency: “wei” | “token”): Promise<void>

Allows a user to initiate an in-channel exchange with the hub by requesting an exchange and proposing an Exchange update type.

Exchange rates used are supplied by the hub, and verified to be within a reasonable range of the exchange rate in the client’s store before moving forward with the exchange.

If the hub does not have sufficient collateral in the channel for the exchange, the exchange will fail. Unlike failed payments, a failing exchange will not trigger autocollateralization of the channel. By default, the hub should deposit enough to facilitate any in channel exchanges up to the exchange limit set by your hub operator. See the hub configuration section for more details, and contact your hub operator to determine how the exchange limits may affect your application

Parameters:

Name Type Description
toSell string the amount of eth/tokens to sell, in wei units
currency "wei" | "token" can either be "wei" or "token" to specify which type of currency you are selling

Returns: Promise<void>

Example:

// propose a token exchange with the hub
await connext.exchange({
  toSell: "100",
  currency: "token",
})
// propose a wei exchange with the hub
await connext.exchange({
  toSell: "10",
  currency: "wei",
})

Note: Due to rounding errors, you may notice that the amount that is actually exchanged within the channel may not be exactly the requested amount.


recipientNeedsCollateral

recipientNeedsCollateral(recipient: Address, amount: Payment): Promise<string | null>

Returns null if the hub has enough collateral in the recipient’s channel to facilitate a payment, and a descriptive string if it cannot.

Insufficiently collateralized payments will fail, but they will trigger the hub’s autocollateralization mechanism. See more about this in the core concepts section of this documentation.

Implementers can use this function to check the status of a recipient’s collateral, and intentionally send a failing payment to start the autocollateralization, then use this function to monitor the status of that collateralization.

Parameters:

Name Type Description
recipient Address the signing wallet address of the payment recipient
amount Payment the amount of the payment, as a Payment type.

Returns: Promise<string | null>

Example:

// establish payment details
const payment: PurchasePaymentRequest = {
  recipient: "0x87djb...",
  amount: {
    wei: "100",
    token: "0"
  },
  type: "PT_CHANNEL"
}
// check if a payee's channel has sufficient collateral before sending payment
const needsCollateral: string | null = await connext.recipientNeedsCollateral({
  recipient: payment.recipient,
  amount: payment.amount,
})

// if needsCollateral exists, trigger autocollateralization
if (needsCollateral) {
  // is a descriptive string, e.g "Channel does not exist yet"
  console.log(needsCollateral)
  try {
    await connext.buy({
      meta: { msg: "Triggering autocollateral of recipient"},
      payments: [payment]
    })
  } catch (e) {
    console.log("Autocollateral triggered by failing payment for recipient!")
  }
}

// `needsCollateral == null` so recipient does not need collateral in channel
// payment should go through instantly

redeem

redeem(secret: string): Promise<{ purchaseId: string }>

Allows a payee to input a secret (previously generated by the payor) to unlock funds.

Upon submission of the secret, the hub unlocks the funds indicated by the secret and sends them to the user redeeming the secret.

Parameters:

Name Type Description
secret string Hex string generated by connext.generateSecret() created by the payor

Returns: Promise<{ purchaseId: string }>

Example:

// first create a secret as payor and add to payment level metadata
const secret = connext.generateSecret()
const payment = {
  meta: { secret },
  recipient: "0x0000...", // linked payments always use empty addr recipients
  amount: {
    wei: "100",
    token: "0",
  }
}
// make the payment
const payorRes = await connext.buy({
  meta: { msg: "linked payment demo" },
  payments: [ payment ]
})
console.log("payor purchaseId", payorRes.purchaseId)

// redeem a linked payment
const payeeRes = await connext.redeem(secret)
console.log("payee purchaseId", payeeRes.purchaseId)

It is important to note that PT_LINK type payments have no specified recipient and can be redeemed by anyone with the secret. Additionally, for the payee, these are custodial payments and not subject to availability or collateral requirements.


requestCollateral

requestCollateral(): Promise<void>

Request that the hub collateralize a user’s channel.

Hub will deposit tokens and/or ETH to the user’s account in accordance with their configured autodeposit parameters. In order to conserve collateral, some hub operators may impose restrictions on who is allowed to request collateral into their channel to avoid griefing.

Contact your hub operator to find out how their configuration may affect your use case, and learn more about hub configuration parameters here and autocollateralization.

Returns: Promise<void>

Example:

await connext.requestCollateral()
// hub will generate a `ProposePendingDeposit` update, where it deposits into the
// users channel from the contract reserves

// unlike user submitted onchain transactions, or onchain transactions with exchanges
// (as with client withdrawals), hub collateral deposits
// are non-blocking operations, and the state may still be updated.

start

start(): Promise<void>

Starts the stateful portions of the Connext client, including internal pollers.

It is important to note that the client is an EventEmitter, so implementers can subscribe to changes in the client state by registering listeners before starting the connext client.

Returns: Promise<void>

Example:

const connext: ConnextClient = getConnextClient({...})
// register listener
// the only event the client emits is `onStateChange`, which is triggered
// each time the connexts internal store is updated.

connext.on('onStateChange', state => {
  console.log('Connext state changed:', state);
})
await connext.start() // start polling

stop

stop(): Promise<void>

Stops the stateful portions of the Connext client.

Returns: Promise<void>

Example:

await connext.stop() // stops all internal polling
// should be used when cleaning up resources

withdraw

withdraw(withdrawal: WithdrawalParameters): Promise<void>

Withdraw funds from a channel.

Partial withdrawals, full withdrawals, and withdrawals with an onchain exchange (i.e. the hub uses contract reserves instead of channel balance to fund an exchange at withdrawal) are supported by the hub.

Currently, token withdrawals are not supported as the hub is trying to maintain its token collateral.

Parameters:

Name Type Description
withdrawal WithdrawalParameters the WithdrawalParameters sent to the hub.

Returns: Promise<void>

Example:

// first generate the withdrawal parameters
const withdrawal: WithdrawalParameters = {
  withdrawalWeiUser: "0", // wei to withdraw from channel balance
  tokensToSell: "0", // tokens to sell and withdraw as wei
  withdrawalTokenUser: "0", // tokens to withdraw from channel balance
  weiToSell: "0", // wei to sell and withdraw as tokens
  recipient: "0xad33f..." // an outside address to receive funds to. does not need a channel
}

// NOTE: at this time, the hub does NOT support token withdrawals
// this is done to conserve collateral complexity by forcing token
// payments and conserving tokens within the channel system.

// this will be enabled very soon!

await connext.stop() // stops all internal polling
// should be used when cleaning up resources

PersistentState

The client’s persistent state includes things that should persist through independent sessions. It is stored as a redux state with the following properties:

channel: ChannelState the latest double signed channel state

channelUpdate: UpdateRequest the latest channel update

latestValidState: ChannelState the latest channel update without pending operations, used for Invalidation updates

activeThreads: ThreadState[] all open threads in the latest channel state

activeInitialThreadStates: ThreadState[] the initial states of all open threads

threadHistory: ThreadHistoryItem[] an array of objects used to track and generate appropriate threadIDs, keyed by the receiver address

lastThreadUpdateId: number the id of the latest thread update to keep hub in sync

syncControllerState: SyncControllerState includes any updates to sync with the hub


RuntimeState

The client’s runtime state includes things that should not persist through sessions. The flags set here can be used by implementers to restrict user actions at a UI level. The can* flags in the runtime state are set by the middleware.

It is stored as a redux state with the following properties:

awaitingOnchainTransaction: boolean true if either channel member has submitted an onchain transaction

canDeposit: boolean true if user can initiate a deposit

canExchange: boolean true if user can initiate an exchange

canWithdraw: boolean true if user can withdraw from channel

canBuy: boolean true if user can make a purchase

canCollateralize: boolean true if hub is able to collateralize users channel

exchangeRate: ExchangeRateState| null the client’s exchange rates

syncResultsFromHub: SyncResult[] all open threads in the latest channel state

updateRequestTimeout: number default amount of time to wait before invalidating update

channelStatus: ChannelStatus the current status of the channel. Can only advance channel state if the channel status is CS_OPEN. Otherwise, clients can contact hub admins offline to solve the dispute gaslessly, or use the ChannelManager functions with the channelState from the client (latest double signed state).