arrow-left
Only this pageAll pages
gitbookPowered by GitBook
1 of 20

REDSHIFT API Documentation

Loading...

Getting Started

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

API

Loading...

Loading...

Loading...

Loading...

Loading...

Libraries

Loading...

Loading...

Loading...

Loading...

Loading...

Definitions

There is a lot of language native to submarine swaps

hashtag
Foundational Definitions

Submarine Swap - an atomic on-chain to off-chain swaps of cryptocurrencies. REDSHIFT currently supports swaps of on-chain assets like Bitcoin, Ether, or DAI for Lightning Bitcoin. Also referred to as just "swap".

HTLC (Hash Time Lock Contract) - A type of transaction that puts on-chain funds in escrow until someone unlocks them with a preimage or the timelock expires.

Preimage - A string of characters returned when successfully paying a Lightning Network invoice. Think of it as a password. It is used to unlock on-chain funds that are locked up in an HTLC.

Liquidity Provider - The counter-party to a swap that will pay the Lightning Invoice in exchange for an on-chain asset like Bitcoin, Ether, or DAI.

Quote - The price a liquidity provider returns when asked to pay an invoice. Denominated in the requested asset.

WebSocket API

hashtag
Introduction

Our WebSocket API uses socket.ioarrow-up-right. You must use a socket.io client to interact with the Redshift WebSocket API.

If you are building a JavaScript or TypeScript application, we have a JavaScript client packagearrow-up-right that does a lot of the heavy lifting for WebSockets.

hashtag
Client Example

hashtag
JavaScript Example

hashtag
Other Language Clients:

C++arrow-up-right

Javaarrow-up-right
Pythonarrow-up-right
Goarrow-up-right
Swiftarrow-up-right
import { WebSocketClient } from "@radar/redshift-api-client";

const client = new WebSocketClient();

await client.connect();

const quoteRequest = {
    market: "BTC_LBTC",
    invoice: "lnbc1m1p0zwsllpp5np23cxgafxcqnk434v86rlnpght09gvnzwtrvy0y704nqtjtffvqdqdgdf4wgztv4uhxcqzpg3awyquek08frfexrphkm9sjwdggkfepm6uxex0lg4m8s0s59d7z8kdf9cajvc00cwl2nzu57qe9n92fv2mzm4hlgctw2x3v0lxcjrrcpul9wen",
    refundAddress: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
};

const quote = await client.requestQuote(quoteRequest);

console.log(quote);

// {
//   orderId: "13283979-2c0b-45e4-b2e0-2a487535822e";
//   expiryTimestampMs: 1578598212267;
//   amount: "0.001056000000000000";
//   details: {
//     payToAddress: "3LKomc2i3rLm2TnA36U4pGH25cFWvRMnJB";
//     redeemScript: "76a914d5846ccb08521347c1d5718ec460656d67427cbf8763752102369b4d174f601239daef6a83bf553092e139901e6ed19ffbcbedcee07be7a4ae6703575709b17576a914c1e27ac884310271f04dd1ebe9eb4e135113a1d08868ac";
//     refundableAtBlockHeight: 612183;
//   }
// }
import io from "socket.io-client";

const socket = io.connect("https://api.redshift.radar.tech/user/v1");

const request = {
    market: "BTC_LBTC",
    invoice: "lnbc1m1p0pv6fepp5kjf5zgm6590w77u3qtg38cjwxwz36l505q3gurl4nl8xw47t33msdqsgdf4wfmnyp9k27tncqzpgwrdthp4apgu7v4s53xc27clm82nkchmylm5huvwdd660q36e82vske04eeesljjyt89q3aglzr9l7l3yxpy5pwfl08unztfrqhcndesqyym288",
    refundAddress: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
};

socket.emit(
  "requestQuote",
  request,
  ({ success, message }: WebSocketResponse<Quote | string>) => {
    console.log("Successful:", success, "Response Message:", message);
  },
);

@radar/htlc

A library used to construct and interact with HTLCs across multiple networks & network models

hashtag
View on Githubarrow-up-right

hashtag
Installation

hashtag
npm

hashtag
yarn

hashtag
Usage - Bitcoin

hashtag
Construct a Bitcoin HTLC

hashtag
Construct a new Bitcoin HTLC with an absolute timelock:

hashtag
Construct a new Bitcoin HTLC with a relative timelock:

hashtag
Construct a Bitcoin HTLC from an existing redeem script:

hashtag
Construct a Bitcoin HTLC with a refund public key instead of address:

hashtag
Interact with the Bitcoin HTLC

Get the HTLC details:

Get the redeem script:

Generate the fund transaction. You can now broadcast it using sendrawtransaction:

Generate the claim transaction. You can now broadcast it using sendrawtransaction:

Generate the refund transaction. You can now broadcast it using sendrawtransaction:

For working examples, view the or Timelock Bitcoin HTLC tests.

hashtag
Usage - Ethereum

hashtag
Construct an Ethereum HTLC

Asset: Ether

Asset: ERC20

hashtag
Interact with the Ethereum HTLC

Get the deployed HTLC contract instance:

Generate, sign, and broadcast the fund transaction using the passed provider:

Generate and broadcast the claim transaction using the passed provider:

Generate and broadcast the refund transaction using the passed provider:

Want to pass in additional tx params? Pass all desired tx params as the last argument:

Don't want to sign and broadcast the transaction? Set shouldBroadcast to false to return the unsigned transaction. You can now broadcast it using eth_sendTransaction:

For working examples, view the or Ethereum HTLC tests.

hashtag
Usage - Stellar

hashtag
Construct a Stellar HTLC

hashtag
Interact with the Stellar HTLC

Build, sign, and broadcast a transaction (envelope) to create an escrow account (escrowPubKey):

Build the fundEnvelope and refundEnvelope for the user:

Once the user funds, pay the invoice to get the payment secret. Use the payment secret to claim the funds. Once broadcast, the escrow account gets merged into the server account. Swap Complete.

For a working example, view the HTLC tests.

hashtag
Testing

hashtag
Build container services for tests

hashtag
Run tests:

hashtag
Additional Information

hashtag
Subnet Naming

Subnets should be named SIMNET, TESTNET, or MAINNET if there is only one instance of the subnet type. If there are two or more instances of a single subnet type (multiple testnets, etc.) then the naming convention should be SUBNETNAME_SUBNETTYPE. e.g. KOVAN_TESTNET.

Absolutearrow-up-right
Relativearrow-up-right
Etherarrow-up-right
ERC20arrow-up-right
Stellararrow-up-right
npm install @radar/htlc
yarn add @radar/htlc
import { HTLC, UTXO } from '@radar/htlc';

const htlc = HTLC.construct(Network.BITCOIN, BitcoinSubnet.SIMNET, {
  paymentHash: 'fba6da3ff596b9c6fabe67d4f728474697640ef6edd9e361c2a46be345112839',
  claimerPublicKey: '0286ab3b59ce3862515b01c8a282edb6011b4eb50c608ab298bfd70f6033f7bc65',
  refundAddress: 'sb1qxnqqm56ta40p3uhtsmtdxglhwuxjk3tul94mq0',
  timelock: {
    type: UTXO.LockType.ABSOLUTE,
    blockHeight: 597732,
  },
});
import { HTLC, UTXO } from '@radar/htlc';

const htlc = HTLC.construct(Network.BITCOIN, BitcoinSubnet.SIMNET, {
  paymentHash: 'fba6da3ff596b9c6fabe67d4f728474697640ef6edd9e361c2a46be345112839',
  claimerPublicKey: '0286ab3b59ce3862515b01c8a282edb6011b4eb50c608ab298bfd70f6033f7bc65',
  refundAddress: 'sb1qxnqqm56ta40p3uhtsmtdxglhwuxjk3tul94mq0',
  timelock: {
    type: UTXO.LockType.RELATIVE,
    blockBuffer: 50,
  },
});
import { HTLC } from '@radar/htlc';

const htlc = HTLC.construct(
  Network.BITCOIN,
  BitcoinSubnet.SIMNET,
  '76a914c15949a2e2a414b5c641f32c4c2ee07be644e165876375210398c9a44bed9f59c6041a574602aab0af6a08f3f0fb847fd9a167f7afd71b8d25670114b27576a9143f1857b3db895b4d481a46e5a0129cb2b04781c88868ac',
);
import { HTLC, UTXO } from '@radar/htlc';

const htlc = HTLC.construct(Network.BITCOIN, BitcoinSubnet.SIMNET, {
  paymentHash: 'fba6da3ff596b9c6fabe67d4f728474697640ef6edd9e361c2a46be345112839',
  claimerPublicKey: '0286ab3b59ce3862515b01c8a282edb6011b4eb50c608ab298bfd70f6033f7bc65',
  refundPublicKey: '029814897bbfe085fcf1611a7100919f31b4424443d694a52b86bbb9ab9447d073',
  timelock: {
    type: UTXO.LockType.RELATIVE,
    blockBuffer: 50,
  },
});
const { details } = htlc;
const { redeemScript } = htlc;
const fundTxHex = htlc.fund(utxos, fundingAmount, privateKey);
const claimTxHex = htlc.claim(
  utxos,
  destinationAddress,
  currentBlockHeight,
  feeTokensPerVirtualByte,
  paymentSecret,
  privateKey,
);
const refundTxHex = htlc.refund(
  utxos,
  destinationAddress,
  currentBlockHeight,
  feeTokensPerVirtualByte,
  privateKey,
);
import { HTLC } from '@radar/htlc';

const htlc = HTLC.construct(Network.ETHEREUM, EthereumSubnet.GANACHE_SIMNET, {
  orderUUID,
  provider: web3.currentProvider,
  assetType: EVM.AssetType.ETHER,
});
import { HTLC } from '@radar/htlc';

const htlc = HTLC.construct(Network.ETHEREUM, EthereumSubnet.GANACHE_SIMNET, {
  orderUUID,
  provider: web3.currentProvider,
  tokenContractAddress,
  assetType: EVM.AssetType.ERC20,
});
const { contract } = htlc;
const txReceipt = await htlc.fund(amount, paymentHash);
const txReceipt = await htlc.claim(paymentSecret);
const txReceipt = await htlc.refund();
const txReceipt = await htlc.refund(true, {
  gas: 200000,
});
const unsignedTx = await htlc.refund(false);
import { HTLC } from '@radar/htlc';

const htlc = HTLC.construct(Network.STELLAR, StellarSubnet.TESTNET, {
  secret: 'SCHMRGINH4CDPUPKBEQZTFZHNRSZKC3NYEFMSUYNDKA4OQK3ZA7JT7C6',
});
await htlc.create();
const fundEnvelope = await htlc.fund(
  userPubKey,
  fundAmount,
  paymentHash,
);

const refundEnvelope = await htlc.refund(
  userPubKey,
  timelockInSeconds,
);
await htlc.claim(paymentSecret);
docker-compose up -d
yarn test

Order States

hashtag
Funding

WaitingForFundingTx - The HTLC has been created and is waiting for an on-chain funding transaction.

WaitingForFundingTxConfirmation - An on-chain funding transaction has been broadcasted but has not been confirmed.

WaitingForAdditionalFundingTxConfirmation - An on-chain funding transaction has been confirmed but has not met the confirmation threshold required to complete the swap. Wait for more confirmations and the order will move to Funded.

PartiallyFunded - The transaction has been partially funded, meaning a transaction has been sent but the total amount of funds sent has not reached the quote amount. Another transaction must be sent so that the total amount of funding is equal to the amount given in the quote.

Funded - The HTLC has been funded to the correct amount. After quote provider pays the Lightning Invoice, they will receive the preimage to unlock the funds.

hashtag
Refund

WaitingForRefundTx - The swap was not completed successfully and HTLC's timelock has passed. A transaction can be sent to reclaim funds.

AddressBlacklistedWaitingForRefundTx - The transaction cannot be completed because the address is blacklisted. Send a refund transaction to reclaim funds.

WaitingForRefundTxConfirmation - A refund transaction has been broadcast to the network but has not confirmed yet.

Refunded - The refund transaction has been confirmed and the funds have been sent back to the user.

hashtag
Other States

Complete - The swap is complete. The invoice has been paid and the funds from the HTLC have been swept by the liquidity provider.

FundWindowElapsed - The window to fund the HTLC has elapsed. Request a new quote to start the swap process again.

Errors

Here is an explanation of the errors you might get returned

hashtag
Order Errors

OrderNotFound - There is no order in the database with that orderId. Double check that the orderId is correct.

InvalidOrderId - The orderId provided is not a valid UUID. Double check that the orderId is correct.

InvalidRefundAddress - The refund address provided is not a valid Bitcoin address. Double check to make sure the address was input correctly.

hashtag
Market Errors

InvalidMarket - An invalid market was provided with the request. For valid markets, see .

InvalidOnchainTicker - The asset ticker provided is not valid. For valid tickers, see .

hashtag
Invoice Errors

InvalidInvoice - The invoice provided is not a valid Lightning Network invoice. You can decode valid Lightning Network invoices using RADAR ION's .

InvoiceExpiresTooSoon - The invoice provided expires too soon.

InvoiceAmountBelowMinimum - Invoice is below the minimum amount for selected market.

InvoiceAmountAboveMaximum - Invoice is above the maximum amount for selected market.

InvoicePaymentNotRoutable - There is no route between the liquidity provider and the invoice recipient. Usually a result of the invoice recipient not having a well connected Lightning node. Can be resolved by having the invoice recipient open a channel to another well connected node like 's.

TooManySwapsInFlight - There are too many swaps happening at once. Can be resolved by waiting and sending another request.

hashtag
Broadcast Transaction

InvalidSignedTxHex - The signed transaction hex is invalid. Double check the transaction hex or generate a different one.

InvalidNetworkOrSubnet - The provided network or subnet is not valid. See

InactiveNetworkOrSubnet - The provided network or subnet is not active.

hashtag
Other Errors

InternalServerError - There was an error during one of the requests. Retry the request.

NoQuoteProvidersAvailable - There are no quote providers available for the particular market. This is very unusual. Contact or wait and retry request.

Market

Invoice Minimum

Invoice Maximum

Min Invoice Expiration

BTC_LBTC

30000 satoshis

1000000 satoshis

2400 sec

ETH_LBTC

1 satoshi

1000000 satoshis

900 sec

Market Documentationarrow-up-right
Asset Abbreviation documentationarrow-up-right
invoice decoderarrow-up-right
RADAR IONarrow-up-right
supportarrow-up-right

Market

Endpoints to retrieve market requirements.

hashtag
Get Markets

GET https://api.redshift.radar.tech/api/markets

This endpoint allows you view available markets

[
    {
        "onchainTicker": string,
        "offchainTicker": string,
        "market": string
    }
]
Something went wrong...better call Craig Wright

hashtag
Example

hashtag
Get Requirements for All Markets

GET https://api.redshift.radar.tech/api/markets/requirements

hashtag
Example

hashtag
Get Requirements for Market

GET https://api.redshift.radar.tech/api/markets/:market/requirements

hashtag
Path Parameters

Name
Type
Description

hashtag
Example

market

string

The market to fetch requirements for.

$ curl https://api.redshift.radar.tech/api/markets

[
    {
        "onchainTicker":"BTC",
        "offchainTicker":"LBTC",
        "market":"BTC_LBTC"
    },
    {
        "onchainTicker":"ETH",
        "offchainTicker":"LBTC",
        "market":"ETH_LBTC"
    }
]
[
    {
        "market": string,
        "payReq": {
            "minExpirationSeconds": string,
            "minBaseUnits": string,
            "maxBaseUnits": string
        }
    },
    {
        "market": string,
        "payReq": {
            "minExpirationSeconds": string,
            "minBaseUnits": string,
            "maxBaseUnits": string
        }
    }
]
Something went wrong...better call Craig Wright
$ curl https://api.redshift.radar.tech/api/markets/requirements

[
    {
        "market":"BTC_LBTC",
        "payReq":{
            "minExpirationSeconds":"2400",
            "minBaseUnits":"30000",
            "maxBaseUnits":"1000000"
        }
    },
    {
        "market":"ETH_LBTC",
        "payReq":{
            "minExpirationSeconds":"900",
            "minBaseUnits":"1",
            "maxBaseUnits":"1000000"
        }
    }
]
{
    "market": string,
    "payReq": {
        "minExpirationSeconds": string,
        "minBaseUnits": string,
        "maxBaseUnits": string
    }
}
InvalidMarket | NoQuoteProvidersForTheRequestedMarket | NoQuoteProvidersAvailable
Something went wrong...better call Craig Wright
$ curl https://api.redshift.radar.tech/api/markets/BTC_LBTC/requirements

{
    "market":"BTC_LBTC",
    "payReq":{
        "minExpirationSeconds":"2400",
        "minBaseUnits":"30000",
        "maxBaseUnits":"1000000"
    }
}

HTTP API

Orderschevron-rightMarketchevron-right

Welcome to the REDSHIFT API

This documentation is your entry point to interacting with submarine swaps programmatically on REDSHIFT

hashtag
What is REDSHIFT?

REDSHIFT is a payment network tool that allows Lightning Network payments to be made using on-chain assets like Bitcoin, Ethereum, and ERC-20 tokens.

It utilizes a technology called Submarine Swap in order to make payments in a trustless way - meaning the on-chain asset is controlled by the original owner until the Lightning Payment is sent.

hashtag
Why is this useful?

There are a number of use cases for using Submarine Swaps to make Lightning Network payments:

Channel Rebalancing: The payment channels that compose the Lightning Network require maintaining sufficient liquidity in order to send and receive payments. REDSHIFT allows the liquidity within these channels to be adjusted without having to close and re-open channels with peers - avoiding on-chain opening and closing fees and maintaining the channel's successful payment routing history. Essentially, you can "top up" your local channel balance with REDSHIFT.

Merchants: Accept more digital currencies without adding additional infrastructure. If you already accept Lightning Network payments, REDSHIFT enables your customers to pay using other currencies while you still receive payment in LBTC.

Consumers: REDSHIFT allows customers to send payments on the Lightning Network without having to operate their own node or trust custodial wallets with their sats. Simply send on-chain assets from the wallet they already use and the Lighting Network invoice will be paid.

hashtag
How does it work?

Submarine Swaps utilize a class of payments called Hash Time Locked Contracts (HTLCs).

HTLCs use hashlocks and timelocks to require that the receiver of a payment either acknowledge receiving the payment prior to a deadline by generating cryptographic proof of payment or forfeit the ability to claim the payment, returning it to the payer.

HTLCs are what make REDSHIFT trustless - neither the payer or receiver need to trust that the other party will make their required payment because ownership conditions are baked into the transaction itself.

hashtag
Routing Node Example Workflow

Alice runs a Lightning Network node that helps route payments through the Lightning Network. She has opened a couple of channels with a handful peers and has been routing payments through them. After a few days, a lot of her balance has accrued on the remote side of her channels.

Instead of closing her lopsided channels and reopening them to regain more local balance, she generates an invoice for her channels with more remote capacity and sends it to REDSHIFT via REDSHIFT's API. REDSHIFT responds with a Bitcoin address and amount to send to the address.

Note: The amount of satoshis Alice will send to the address is typically greater than the amount of satoshis on the invoice. So in Alice's case, she might have to send 100,500 satoshis on-chain in order to fulfill her 100,000 sat invoice. This spread is how liquidity providers (the people paying the Lightning Invoice) make money and cover the cost of the on-chain transaction required to claim their funds after paying the Lightning Invoice that Alice provided.

Alice sends the Bitcoin to the prescribed address and her Lightning Invoice gets paid, pushing the remote balance of the channel back over to her local side, enabling her to send more outgoing payments through the channel without having to close and reopen it.

hashtag
Merchant / Consumer Example Workflow

Alice wants to pay a 100,000 satoshi Lightning Network invoice with an on-chain asset like Ether. She sends the invoice to REDSHIFT via the REDSHIFT API or a REDSHIFT widget embedded on a webpage. REDSHIFT responds with an unsigned Ethereum transaction, which contains the contract address, input data, and amount of ether to send.

Note: The amount of value of the Ether Alice will send to the address is typically greater than the value of satoshis on the invoice. Let's say that one satoshi is worth $1 and one Ether is worth $1. In this case, Alice might have to send 103,000 Ether on-chain in order to fulfill her 100,000 sat invoice. This spread is how liquidity providers (the people paying the Lightning Invoice) make money and cover the cost of the on-chain transaction required to claim their funds after paying the Lightning Invoice that Alice provided.

Alice sends the prescribed amount of Ether to the address sent back from her REDSHIFT request. These funds are housed in an on-chain escrow protected by HTLCs.

There are now only two ways to claim these funds: either by the liquidity provider using the preimage revealed after paying Alice's Lightning Invoice or by Alice after the timelock has expired.

In most cases, the invoice will be paid and the on-chain asset will be claimed by the liquidity provider. If the invoice is unable to be paid, Alice will be able to re-claim her funds after the timelock expires. This isn't typical but can happen if a route cannot be found.

Asset & Market Abbreviations

hashtag
Asset Abbreviations

hashtag
onChainTickers

BTC - Bitcoin

ETH - Ether

hashtag
offChainTickers

LBTC - Lightning Bitcoin (Bitcoin locked up in the Lightning Network)

hashtag
Market Abbreviations

We combine asset abbreviations with an underscore to define markets in REDSHIFT. We currently support the following mainnet market pairs:

BTC_LBTC - Bitcoin to Lightning Bitcoin

ETH_LBTC - Ether to Lightning Bitcoin

hashtag
Testnet Market Abbreviations

When working in a testing environment, we append a "T" to the front of UTXO testnet market asset pairs and "K" to Kovan (testnet) Ether market asset pairs. We support the following testnet markets:

TBTC_LTBTC - Testnet Bitcoin to Lightning Testnet Bitcoin

KETH_LTBTC - Testnet Ether to Lightning Testnet Bitcoin

hashtag
Simnet Market Abbreviations

Like testnet, we append an "S" to the beginning of market assets on simnet. We support the following simnet markets:

SBTC_LSBTC - Simnet Bitcoin to Lightning Simnet Bitcoin

SETH_LSBTC - Simnet Ether to Lightning Simnet Bitcoin

Tutorials

Tutorials to get a project up and running using the REDSHIFT API

Ether Swap Tutorialchevron-right

@radar/lnrpc

A Typescript gRPC client for LND with support for all LND sub-servers.

hashtag
View on Githubarrow-up-right

arrow-up-right arrow-up-right arrow-up-right arrow-up-right

Originally forked from Matt-Jensen/lnrpcarrow-up-right.

hashtag
Features

  • Auto-generates clients and Typescript type definitions using a target release tag

  • Supports all LND sub-servers

  • Wraps requests in promises

  • Easily setup SSL and Macaroons

hashtag
Installation

Notes:

  • Ensure you have an lnd instance running with --no-macaroons, unless you provide macaroon authentication to your lnrpc instance when created.

  • If you want to interact with the LND sub-servers, ensure that LND was compiled with the necessary sub-server build tags.

  • If the following error is thrown in the consuming application, run npm rebuild:

hashtag
Usage

This package exports a create function for the main gRPC server as well as each sub-server:

You can also import the create function for the main gRPC server using the default import:

If you want to interact with all servers, wrap the functions in a class or object for easy initialization:

hashtag
Usage Example - Main Server

Connecting to an lnd instance at localhost:10001.

hashtag
Options Example - Main Server

hashtag
API Reference

.

hashtag
Usage With BTCPayServer

By default lnrpc assumes SSl certificate pinning. In order to use lnrpc with a service (like BTCPayServer) which manages your certification, you'll have to opt to disable certificate pinning by passing { tls: false } within your lnrpc configuration.

hashtag
Contributing

Clone Repository & Install Dependencies

Change LND gRPC release version

To change the gRPC definitions used for all auto-generated types and RPC methods edit the config.lnd_release_tag value in package.json to the desired and run the following:

Newly generated type definitions will be available in ./generated. You can now delete the old proto file inside the lnd directory. Use the generated type definitions to update the types in src/types/rpc.

hashtag
License

This project is licensed under the MIT License.

Instantiates all gRPC services

  • uint64/int64 types cast to string to prevent precision loss

  • lnd/lnrpcarrow-up-right
    All main server (lnrpc) methods documentation can be found herearrow-up-right
    LND release tagarrow-up-right
    npm install @radar/lnrpc
    # OR
    yarn add @radar/lnrpc
    Error: Failed to load gRPC binary module because it was not installed for the current system
    import {
      createAutopilotRpc,
      createChainRpc,
      createInvoicesRpc,
      createLnRpc,
      createRouterRpc,
      createSignRpc,
      createWalletRpc,
      createWatchtowerRpc,
      createWtClientRpc,
    } from '@radar/lnrpc';
    import createLnRpc from '@radar/lnrpc';
    import createLnRpc, {
      AutopilotRpc,
      ChainRpc,
      createAutopilotRpc,
      createChainRpc,
      createInvoicesRpc,
      createRouterRpc,
      createSignRpc,
      createWalletRpc,
      createWatchtowerRpc,
      createWtClientRpc,
      InvoicesRpc,
      LnRpc,
      RouterRpc,
      RpcClientConfig,
      SignRpc,
      WalletRpc,
      WatchtowerRpc,
      WtClientRpc,
    } from '@radar/lnrpc';
    
    export class Lightning {
      public static lnrpc: LnRpc;
      public static autopilotrpc: AutopilotRpc;
      public static chainrpc: ChainRpc;
      public static invoicesrpc: InvoicesRpc;
      public static routerrpc: RouterRpc;
      public static signrpc: SignRpc;
      public static walletrpc: WalletRpc;
      public static watchtowerrpc: WatchtowerRpc;
      public static wtclientrpc: WtClientRpc;
    
      /**
       * Initialize gRPC clients for the main server and all sub-servers
       * @param config The RPC client connection configuration
       */
      public static async init(config: RpcClientConfig): Promise<void> {
        this.lnrpc = await createLnRpc(config);
        this.autopilotrpc = await createAutopilotRpc(config);
        this.chainrpc = await createChainRpc(config);
        this.invoicesrpc = await createInvoicesRpc(config);
        this.routerrpc = await createRouterRpc(config);
        this.signrpc = await createSignRpc(config);
        this.walletrpc = await createWalletRpc(config);
        this.watchtowerrpc = await createWatchtowerRpc(config);
        this.wtclientrpc = await createWtClientRpc(config);
      }
    }
    import createLnRpc from '@radar/lnrpc';
    
    (async () => {
      const lnRpcClient = await createLnRpc(config);
    
      // All requests are promisified and typed
      const { confirmedBalance } = await lnRpcClient.walletBalance();
    
      // ...and you're off!
      console.log(confirmedBalance);
    
      // subscribe to LND server events
      const subscriber = await lnRpcClient.subscribeInvoices();
      subscriber.on('data', invoice => {
        console.log(invoice); // do something with invoice event
      });
    })();
    import createLnRpc from '@radar/lnrpc';
    
    (async () => {
      const lnRpcClient = await createLnRpc({
        /*
         * By default lnrpc connects to `localhost:10001`,
         * however we can point to any host.
         */
        server: '173.239.209.2:3001',
    
        /*
         * By default  lnrpc looks for your tls certificate at:
         * `~/.lnd/tls.cert`, unless it detects you're using macOS and
         * defaults to `~/Library/Application\ Support/Lnd/tls.cert`
         * however you can configure your own SSL certificate path like:
         */
        tls: './path/to/tls.cert',
    
        /*
         * You can also provide a TLS certificate directly as a string
         * (Just make sure you don't commit this to git).
         * Overwrites: `tls`
         */
        cert: process.env.MY_SSL_CERT,
    
        /*
         * Optional path to configure macaroon authentication
         * from LND generated macaroon file.
         */
        macaroonPath: './path/to/data/admin.macaroon',
    
        /*
         * Optional way to configure macaroon authentication by
         * passing a hex encoded string of your macaroon file.
         * Encoding: `xxd -ps -u -c 1000 ./path/to/data/admin.macaroon`
         * Details: https://github.com/lightningnetwork/lnd/blob/dc3db4b/docs/macaroons.md#using-macaroons-with-grpc-clients
         */
        macaroon: process.env.MY_MACAROON_HEX,
      });
    
      try {
        const getInfoResponse = await lnRpcClient.getInfo();
        console.log(getInfoResponse);
      } catch (error) {
        console.error(error);
      }
    })();
    git clone git@github.com:RadarTech/lnrpc.git && cd $_
    
    npm install
    # OR
    yarn
    npm run update-protos
    # OR
    yarn update-protos
    
    # AND
    
    npm run generate
    # OR
    yarn generate
    CircleCI
    Known Vulnerabilities
    NPM Version
    License

    @radar/redshift-api-client

    REDSHIFT HTTP & WebSocket Client Library

    hashtag
    View on Githubarrow-up-right

    hashtag
    Installation

    hashtag
    npm

    hashtag
    yarn

    hashtag
    Usage - REDSHIFT Client

    The REDSHIFT client acts as a wrapper for the HTTP & WebSocket clients.

    hashtag
    Import

    hashtag
    Instantiation

    Mainnet

    By default, the REDSHIFT client targets mainnet

    If you prefer to be explicit, you can pass RedshiftApiUrl.MAINNET into the constructor

    Testnet

    hashtag
    Client Access

    The http and ws getters can be used to access the HTTP & WebSocket client methods.

    http - Get Markets Example

    Get the active markets

    ws - Connect Example

    Establish a connection to the REDSHIFT WebSocket API

    hashtag
    Usage - HTTP Client

    The HTTP client can be used to interact with the REDSHIFT HTTP endpoints.

    hashtag
    Import

    hashtag
    Instantiation

    Mainnet

    By default, the HTTP client targets mainnet

    If you prefer to be explicit, you can pass RedshiftApiUrl.MAINNET into the constructor

    Testnet

    hashtag
    Methods

    Get Markets

    Get the active markets

    Get Orders

    Get all swap orders for a specific invoice

    Get Order

    Get a single swap order

    Get Order State

    Get the state of a single order

    Get Order Fund Details

    Get the fund details for an order

    Get Order Transactions

    Get the transactions relating to an order

    Get Order Refund Details

    Get the refund details for a single order

    hashtag
    Usage - WebSocket Client

    The WebSocket client can be used to interact with the REDSHIFT WebSocket endpoints. Many WebSocket interactions are promisified to provide a better developer experience.

    hashtag
    Import

    hashtag
    Instantiation

    Mainnet

    By default, the WebSocket client targets mainnet

    If you prefer to be explicit, you can pass RedshiftApiUrl.MAINNET into the constructor

    Testnet

    hashtag
    Methods

    Connect

    Establish a connection to the REDSHIFT WebSocket API

    Disconnect

    Disconnect from the REDSHIFT WebSocket API

    Request Quote

    Request a quote for the provided invoice and selected on-chain asset

    Subscribe to Order State

    Subscribe to order state updates for the provided order id

    On Order State Changed

    Execute the callback function when an order state update is received

    Unsubscribe from Order State

    Unsubscribe from order state updates for the provided order id

    Subscribe to Block Height

    Subscribe to block height updates for the provided network and subnet

    On Block Height Changed

    Execute the callback function when a block height update is received

    Unsubscribe from Block Height

    Unsubscribe from block height updates for the provided network and subnet

    Request Refund Details

    Request refund details for a specific swap order

    Broadcast Transaction

    Broadcast signed transaction hex to your network of choice

    npm install @radar/redshift-api-client
    yarn add @radar/redshift-api-client
    import { RedshiftClient } from '@radar/redshift-api-client';
    const client = new RedshiftClient();
    const client = new RedshiftClient(RedshiftApiUrl.MAINNET);
    const client = new RedshiftClient(RedshiftApiUrl.TESTNET);
    const markets = await client.http.getMarkets();
    await client.ws.connect();
    import { HttpClient } from '@radar/redshift-api-client';
    const client = new HttpClient();
    const client = new HttpClient(RedshiftApiUrl.MAINNET);
    const client = new HttpClient(RedshiftApiUrl.TESTNET);
    const markets = await client.getMarkets();
    const orders = await client.getOrders(invoice);
    
    // Or filter by the on-chain asset used to fund the swap
    const orders = await client.getOrders(invoice, OnChainTicker.ETH);
    const order = await client.getOrder(orderId);
    const state = await client.getOrderState(orderId);
    const state = await client.getOrderFundDetails(orderId);
    const state = await client.getOrderTransactions(orderId);
    const details = await client.getOrderRefundDetails(orderId);
    import { WebSocketClient } from '@radar/redshift-api-client';
    const client = new WebSocketClient();
    const client = new WebSocketClient(RedshiftApiUrl.MAINNET);
    const client = new WebSocketClient(RedshiftApiUrl.TESTNET);
    await client.connect();
    client.disconnect();
    await client.requestQuote(quoteRequest);
    await client.subscribeToOrderState(orderId);
    client.onOrderStateChanged(stateUpdate => {
      console.log(stateUpdate);
    });
    await client.unsubscribeFromOrderState(orderId);
    await client.subscribeToBlockHeight(network, subnet);
    client.onBlockHeightChanged(blockHeightUpdate => {
      console.log(blockHeightUpdate);
    });
    await client.unsubscribeFromBlockHeight(network, subnet);
    const details = await client.requestRefundDetails(orderId);
    const { txId } = await client.broadcastTransaction(txRequest);

    Orders

    Endpoints to interact with and retrieve orders.

    hashtag
    Get Orders

    GET https://api.redshift.radar.tech/api/orders

    This endpoint allows you to get orders via the lightning invoice and payment asset. Because an actual invoice could potentially exceed the maximum URL length, we use the invoice hash.

    hashtag
    Query Parameters

    Name
    Type
    Description

    hashtag
    Example

    hashtag
    ​Get Order

    GET https://api.redshift.radar.tech/api/orders/:id

    hashtag
    Path Parameters

    Name
    Type
    Description

    hashtag
    Example

    hashtag
    Get Order State

    GET https://api.redshift.radar.tech/api/orders/:id/state

    hashtag
    Path Parameters

    Name
    Type
    Description

    hashtag
    Example

    hashtag
    ​Get Order Funding Details

    GET https://api.redshift.radar.tech/api/orders/:id/fundDetails

    hashtag
    Path Parameters

    Name
    Type
    Description

    hashtag
    Example

    hashtag
    Get Order Transactions

    GET https://api.redshift.radar.tech/api/orders/:id/transactions

    hashtag
    Path Parameters

    Name
    Type
    Description

    hashtag
    Example

    hashtag
    Get Refund

    GET https://api.redshift.radar.tech/api/orders/:id/refund

    hashtag
    Path Parameters

    Name
    Type
    Description

    hashtag
    Example

    invoiceHash

    string

    The SHA256 hash of the Lightning invoice used to request the quote.

    onchainTicker

    string

    The ticker code for the payment asset.

    id

    string

    The id associated with the order.

    id

    string

    The id associated with the order.

    id

    string

    The id associated with the order.

    id

    string

    The id associated with the order.

    id

    string

    The id associated with the order.

    [
     {
      "id": string,
      "market": string,
      "onchainTicker": string,
      "createdAt": string,
      "state": string,
      "payToAddress": string,
      "amount": string | BigNumber
      "amountPaid": string | BigNumbe
      "expiryTimestamp": Date
     }
    ]
    InvalidOnchainTicker
    InvalidInvoiceHash
    Something went wrong...better call Craig Wright
    $ curl https://api.redshift.radar.tech/api/orders?invoiceHash=0a942325eb07d744c3b98a3b8e0d50bee02d9d2fe4f0cd1734ff3ce8b60a994c
    [
        {
            "id": "a61131f7-0b66-460e-b3d8-37d59f94b8aa",
            "market": "BTC_LBTC",
            "onchainTicker": "BTC",
            "createdAt": "2019-11-05T20:11:08.821Z",
            "state": "Complete",
            "payToAddress": "383HpQyScZvbyXJrdD6WCnLGRBeeQMBDX7",
            "amount": "0.0003559",
            "amountPaid": "0.0006669"
        },
        {
            "id": "0ec6f515-5d61-4998-ba93-97a4e51164e9",
            "market": "BTC_LBTC",
            "onchainTicker": "BTC",
            "createdAt": "2019-11-05T20:05:45.168Z",
            "state": "FundWindowElapsed",
            "payToAddress": "363ZmnA5BYYQq3FQhTBmYNcEKY5CwWoekS",
            "amount": "0.0003593",
            "amountPaid": "0"
        }
    ]
    
    {
      "id": string,
      "market": string,
      "onchainTicker": string,
      "createdAt": string,
      "state": string,
      "payToAddress": string,
      "amount": string | BigNumber
      "amountPaid": string | BigNumbe
      "expiryTimestamp": Date
     }
    InvalidOrderId
    OrderNotFound
    Something went wrong...better call Craig Wright
    $ curl https://api.redshift.radar.tech/api/orders/206820ee-0136-436b-8d0a-f288eb4011d0
    
    {
        "market": "ETH_LBTC",
        "onchainTicker": "ETH",
        "createdAt": "2020-01-05T01:59:48.385Z",
        "state": "Complete",
        "payToAddress": "0x46340430971885eFfA5757eE03356eD228258ac0",
        "amount": "0.37583397",
        "amountPaid": "0.37583397",
        "invoice": "lnbc6500u1p0pzskxpp55n4d4gaahlnvrqg3exlmuvwtppkgu0h5h79wyhznxkljjh4qyw7qdqjg9kxcgrfdcsycsj5gvcqzpgxqyz5vq9rqymmgvr5h4kejvd99qe7xdpqakkraqrpdrpwhpmpwuv5ha830k50jqjv84t6a49gu633ykg3xwr2k2hjfeasq08l0lwhgkmy63s3spscplrl",
        "paymentHash": "a4eadaa3bdbfe6c18111c9bfbe31cb086c8e3ef4bf8ae25c5335bf295ea023bc",
        "paymentPreimage": "4073e2c3affd699b93959029a7d481ac4daf5a98bda8f040c6e8fbc0cf791001"
    }
    WaitingForFundingTx | WaitingForFundingTxConfirmation | FundWindowElapsed
    InvalidOrderId
    OrderNotFound
    Something went wrong...better call Craig Wright
    $ curl https://api.redshift.radar.tech/api/orders/206820ee-0136-436b-8d0a-f288eb4011d0
    
    "Complete"
    {
        "orderId": string,
        "expiryTimestampMs": number,
        "amount": string,
        "details": {
            "redeemScript": string,
            "payToAddress": string,
            "refundableAtBlockHeight": number
        }
    }
    InvalidOrderId
    OrderNotFound
    Something went wrong...better call Craig Wright
    $ curl https://api.redshift.radar.tech/api/orders/206820ee-0136-436b-8d0a-f288eb4011d0/fundDetails
    
    {
        "orderId": "206820ee-0136-436b-8d0a-f288eb4011d0",
        "expiryTimestampMs": 1578190188508,
        "amount": "0.375833970000000000",
        "details": {
            "unsignedFundingTx": {
                "to": "0x46340430971885eFfA5757eE03356eD228258ac0",
                "data": "0x3fdcdd1e206820ee0136436b8d0af288eb4011d000000000000000000000000000000000a4eadaa3bdbfe6c18111c9bfbe31cb086c8e3ef4bf8ae25c5335bf295ea023bc",
                "value": "375833970000000000"
            }
        }
    }
    [
        {
            "type": string,
            "id": string
        }
    ]
    InvalidOrderId
    OrderNotFound
    Something went wrong...better call Craig Wright
    $ curl https://api.redshift.radar.tech/api/orders/206820ee-0136-436b-8d0a-f288eb4011d0/transactions
    
    [
        {
            "type": "FUND",
            "id": "0x46ac9daadba75fd2af42584dd0cc3ddc1c66d2d9fa1af4f7af8f416cd5c6e83f"
        }
    ]
    // Bitcoin Refund
    {
        "market": string,
        "state": string,
        "blocksRemaining": number,
        "refundableAtBlockHeight": number,
        "refundableBalance": string,
        "details": {
            "refundAddress": string,
            "redeemScript": string,
            "currentBlockHeight": number,
            "feeTokensPerVirtualByte": number,
            "utxos": [
                {
                    "txId": string,
                    "index": number,
                    "tokens": number
                }
            ]
        }
    }
    
    // Ethereum Refund
    {
        "market": string,
        "state": string,
        "blocksRemaining": number,
        "refundableAtBlockHeight": number,
        "refundableBalance": string,
        "details": {
            "to": string,
            "data": string
        }
    }
    InvalidOrderId
    OrderNotFound
    Something went wrong...better call Craig Wright
    // Bitcoin Order
    $ curl https://api.redshift.radar.tech/api/orders/53f27403-98da-4e08-8578-04ec6cfba4b1/refund
    
    {
        "market": "BTC_LBTC",
        "state": "Complete",
        "blocksRemaining": 0,
        "refundableAtBlockHeight": 607878,
        "refundableBalance": "0.0005263",
        "details": {
            "refundAddress": "bc1q50qmmgf3mtp95qesqq27h8gsdx9syuzv9p8qfa",
            "redeemScript": "76a9148ec7c524f0cfca47daaa56827c76d94e0ea47c2c8763752103698deb28d5807611b0545b2c0b59ea4ae84393b2e22b7f131f1b89694b43749a6703864609b17576a914a3c1bda131dac25a03300015eb9d10698b02704c8868ac",
            "currentBlockHeight": 612073,
            "feeTokensPerVirtualByte": 30,
            "utxos": [
                {
                    "txId": "d2a153c4024152a06cb1ee6e5da1fac3548085ed77dc75f438586c37fed26678",
                    "index": 1,
                    "tokens": 52630
                }
            ]
        }
    }
    
    // Ethereum Order
    $ curl https://api.redshift.radar.tech/api/orders/206820ee-0136-436b-8d0a-f288eb4011d0/refund
    
    {
        "market": "ETH_LBTC",
        "state": "Complete",
        "blocksRemaining": 0,
        "refundableAtBlockHeight": 9224305,
        "refundableBalance": "0.37583397",
        "details": {
            "to": "0x46340430971885eFfA5757eE03356eD228258ac0",
            "data": "0xdfdecfaf206820ee0136436b8d0af288eb4011d000000000000000000000000000000000"
        }
    }

    @radar/redshift-utils

    Utilities used across REDSHIFT codebases

    hashtag

    hashtag
    Installation

    @radar/redshift-types

    Common types used across REDSHIFT codebases

    hashtag

    hashtag
    Installation

    Ether Swap Tutorial

    In this tutorial, we'll integrate the into a Vue frontend that can be used to execute a Kovan Testnet Ether (kETH) <-> Lightning Testnet Bitcoin (ltBTC) submarine swap.

    hashtag
    Prerequisites

    hashtag

    hashtag
    npm

    hashtag
    yarn

    hashtag
    Usage - Validator

    hashtag
    Import

    hashtag
    Methods

    Validate Network

    Determine if the passed network is valid

    Validate On-Chain Ticker

    Determine if the passed on-chain ticker is valid

    Validate Market

    Determine if the passed market is valid

    Validate UUID

    Determine if the passed UUID is valid

    Validate Base58Check

    Determine if the passed string is valid base58check

    Validate Bech32

    Determine if the passed string is valid bech32

    Validate Base58Check or Bech32

    Determine if the passed string is valid base58check or bech32

    Validate Hex

    Determine if the passed string is a valid hex

    Validate Network & Subnet

    Determine if the passed network and subnet are valid

    View on Githubarrow-up-right
    hashtag
    npm

    hashtag
    yarn

    hashtag
    Usage

    Update typeRoots in tsconfig.json for declarations access:

    View on Githubarrow-up-right
    npm install @radar/redshift-types
    npm install @radar/redshift-utils
    yarn add @radar/redshift-utils
    import { validator } from '@radar/redshift-utils';
    const isValid = await validator.isValidNetwork(network);
    const isValid = await validator.isValidOnchainTicker(ticker);
    const isValid = await validator.isValidMarket(market);
    const isValid = await validator.isValidUUID(uuid);
    const isValid = await validator.isValidBase58Check(s);
    const isValid = await validator.isValidBech32(s);
    const isValid = await validator.isValidBase58CheckOrBech32(s);
    const isValid = await validator.isValidHex(s);
    const isValid = await validator.isValidNetworkAndSubnet(network, subnet);
    yarn add @radar/redshift-types
    "typeRoots": ["node_modules/@radar/redshift-types/src/declarations", "node_modules/@types"]
    Install MetaMask

    MetaMask is an Ethereum wallet and web3 provider running inside a browser extension. Install MetaMaskarrow-up-right if not already installed. You can request testnet ether from https://faucet.kovan.networkarrow-up-right if you do not have any.

    hashtag
    Clone this repository

    hashtag
    Navigate to the sample project directory

    hashtag
    Install dependencies

    hashtag
    Run the application

    hashtag
    Facilitating a Swap

    hashtag
    Overview

    In this scenario, a user arrives at the app with a Lightning invoice that they would like REDSHIFT to pay in exchange for ETH. The exchange is atomic and completely trustless. The user will send their ETH to the swap contractarrow-up-right, which can only be claimed by REDSHIFT once the invoice has been paid. If REDSHIFT fails to pay the invoice, the user can reclaim their funds from the contract after a couple hours.

    hashtag
    Fetch Initial Information

    We'll begin by taking a look at the Startarrow-up-right page.

    First, we must fetch two important pieces of information from REDSHIFT: The active markets and market requirements.

    The markets are rather self-explanatory. They tell us which markets REDSHIFT is currently servicing.

    The market requirements can help inform us if the invoice will be rejected by REDSHIFT without making a round-trip to the server. This call is optional as the server will return the same validation error, but we can offer a better user experience by performing this check on the client-side. The market requirements include the minimum time until invoice expiration, minimum invoice amount, and maximum invoice amount that will be accepted by REDSHIFT.

    In our app, we make both calls at the same time as the page is created:

    hashtag
    Gathering & Validating User Input

    Now that we have the markets and market requirements, we'll collect information from the user required to perform the swap.

    At a minimum, we require the invoice that REDSHIFT will pay. If your application only supports one on-chain payment asset, ETH for example, then you will not need to collect the payment asset from the user.

    We also support bitcoin payments in our app, so the user is required to select a payment asset.

    To provide a better user experience, we'll validate the invoice on the client-side. To do so, we use bolt11-decoderarrow-up-right, a lighter version of bolt11arrow-up-right, to decode the invoice.

    If the invoice does not decode successfully, then it is invalid:

    Once the user has input a valid invoice and selected a market, we have enough information to check if the market requirements have been met.

    We'll perform this check when the user clicks Pay Invoice. Alternatively, you could run this validation when the input or select change event fires.

    You can see this call in action inside the initiateSwap method:

    If the invoice does not meet the market requirements, the error is set on the input and code execution is stopped.

    hashtag
    Requesting a Quote

    Once we've validated the information provided by the user, we're ready to request a quote from REDSHIFT.

    This is a simple process that involves two steps; establish a WebSocket connection and request the quote:

    The quote response will look like this:

    Quote Field

    Description

    orderId

    A unique identifier for your order. This is required to execute a refund transaction in the event REDSHIFT fails to pay the invoice.

    expiryTimestampMs

    The timestamp in milliseconds that the quote will expire if the user does not take action. We use this value to implement the quote expiration timer in the app. The action required to stop the quote expiration timer varies based on the payment asset. For Bitcoin, the timer will stop once a funding transaction is seen in the mempool. For Ethereum assets, the timer will stop when a funding transaction confirms.

    amount

    The amount the user must pay denominated in the payment asset that they selected (tBTC or kETH in this sample).

    details

    Quote details that are specific to the chosen market.

    unsignedFundingTx

    The unsigned Ethereum funding transaction. When using metamask, this object can be passed directly into web3.sendTransaction to initiate payment.

    hashtag
    Payment

    We now have everything that we need to request payment from the user. Move to the Paymentarrow-up-right page for this part of the tutorial.

    To provide a good UX, we'll subscribe to order state updates and present them to the user.

    You can subscribe to state updates using the following method:

    Once subscribed, we must attach an event handler that gets fired when the order state changes. In this sample, we'll feed the state update event to a method called handleStateChange that will update the state for display, increase the progress bar completion percentage, and populate the payment proof once complete:

    As you may have gathered from the above description, not all state updates share the same schema. There are three types of state updates.

    Update Type

    Description

    GeneralStateUpdate

    This is the most basic state update, which contains the orderId and state. General state updates are returned when the update did not involve a transaction confirmation or invoice payment. Both TxConfirmedStateUpdate and SwapCompleteStateUpdate extend this type.

    TxConfirmedStateUpdate

    In addition to the orderId and state fields, this update type returns a transactionId. This type is used when notifying the subscriber of a partial fund, fund, or refund transaction confirmation.

    SwapCompleteStateUpdate

    In addition to the orderId and state fields, this update type returns a preimage. This type is used when notifying the subscriber of an invoice payment. The preimage is the proof of payment.

    Now that our state update listener is hooked up, we're ready to accept payment from the user.

    In this example, all MetaMask interactions are handled through the metamask objectarrow-up-right. We'll skip over many of the actions required to connect and communicate with MetaMask as they are not specific to REDSHIFT.

    When the user clicks the Send Payment button, we need to pass the unsigned funding transaction to MetaMask using the sendTransaction RPC call. This will pop up the MetaMask window so the user can sign the transaction.

    In this example, we use the MetaMask provider to make the RPC call directly:

    Note that this code can be simplified by using a library like web3 or ethers.js.

    Once signed, MetaMask will broadcast the transaction automatically. The order state update listener will take over from here. Upon invoice payment, the progress bar will be set to 100%, the proof of payment will be populated, and the Start Another Swap button will be visible.

    hashtag
    Facilitating a Refund

    NOTE: In a real application, the refund details should be provided to the user as a file download before they're allowed to fund the swap. This sample only demos the refund flow when navigating directly from a failed swap.

    If REDSHIFT fails to pay the invoice, the user must be able to reclaim the funds that they sent to the swap contract.

    Open the Refundarrow-up-right page to view the sample refund flow.

    In this example, the user is responsible for refund transaction submission. This is not strictly required. Any address is capable of signing and broadcasting the refund transaction. Regardless of who broadcasts this transaction, the funds will always be returned to the address that initially funded the swap. This could be used to submit the refund transaction on behalf of the user when the timelock expires, which offers a better UX.

    We cannot allow the user to broadcast the refund transaction immediately following invoice payment failure. Any refund transaction mined before the block timelock is met will fail. The ether swap timelock is currently set to 480 blocks, which means that the user must wait roughly 2 hours before refund transaction submission.

    To begin the process, we'll fetch the refund details using the order id of the failed swap:

    The ether refund details contain the following information:

    Refund Field

    Description

    market

    The market of the order, which contains the on-chain and off-chain asset tickers. ETH_LBTC, for example.

    state

    The active order state. REDSHIFT will return refund details regardless of the order state. This field can be used in client-side validations to prevent the user from submitting a refund transaction for an order that's already complete or refunded.

    blocksRemaining

    The number of blocks remaining until the timelock expires and the refund transaction can be submitted.

    refundableAtBlockHeight

    The block height at which the timelock expires and the refund transaction can be submitted.

    refundableBalance

    The balance that is available for refund. Note that this field will not be decreased once the refund is complete.

    If blocksRemaining is greater than 0 then we know that the refund transaction cannot be submitted yet. Instead, we'll display a block countdown to timelock expiration.

    To accomplish this, we'll subscribe to the Ethereum block height using the REDSHIFT WebSocket API and update the UI when a new block is mined:

    Once blocksUntilRefundable is less than or equal to 0, we can enable the Get Refund button and allow the user to submit the refund transaction.

    From here, we use the same approach as the funding transaction. The refund transaction details are passed to the sendTransaction RPC call and the progess bar is updated using the order state subscription. Upon refund confirmation, the progress bar will be set to 100% and the Start Another Swap button will be visible.

    REDSHIFT Javascript SDKarrow-up-right
    git clone https://github.com/RadarTech/redshift-monorepo.git
    cd redshift-monorepo/samples/sdk-usage-vue
    yarn
    yarn start
    async created() {
      [this.markets, this.requirements] = await Promise.all([
        redshift.http.getMarkets(),
        redshift.http.getMarketRequirements(),
      ]);
    }
    /**
     * Validate the passed invoice. If valid, return the decoded invoice
     * @param invoice The bolt11 invoice
     */
    isValidInvoice(invoice: string) {
      try {
        const decodedInvoice = decode(invoice);
        return {
          decodedInvoice,
          isValid: true,
        };
      } catch (error) {
        return {
          isValid: false,
        };
      }
    }
    // Ensure the invoice meets the market requirements
    const decodedInvoice = decode(data.invoice);
    const invoiceMeetsRequirements = this.marketRequirementsSatisfied(
      decodedInvoice,
      data.market,
    );
    if (!invoiceMeetsRequirements) return;
    // Establish a WebSocket connection
    await redshift.ws.connect();
    
    // Request the quote
    const quote = await redshift.ws.requestQuote({
      invoice: data.invoice,
      market: data.market,
    });
    { 
       "orderId":"56f970d4-24cc-4112-8e1a-4bef7becbee2",
       "expiryTimestampMs":1571943386485,
       "amount":"0.005068660000000000",
       "details":{ 
          "unsignedFundingTx":{ 
             "to":"0xd4589fB5b5ABB44e1A8cb95CfF0Ca9E0e78D9D5d",
             "data":"0x3fdcdd1e56f970d424cc41128e1a4bef7becbee20000000000000000000000000000000009495061c40a27c05ca574ff5c4d61869e4a936a003b13f8050d5aeba0ecfc7d",
             "value":"5068660000000000"
          }
       }
    }
    await redshift.ws.subscribeToOrderState(this.orderId);
    redshift.ws.onOrderStateChanged(this.handleStateChange);
    /**
     * Call eth_sendTransaction. Display an error message if
     * an error is thrown.
     * @param address The active address
     */
    async sendTransaction(address: string) {
      try {
        const { data, to, value } = this.tx; // Tx values from REDSHIFT
        await metamask.sendTransaction({
          data,
          to,
          value: value ? decToHex(value as string) : undefined,
          from: address,
        });
      } catch (error) {
        this.metamaskError = error.message;
      }
    }
    this.refund.details = await redshift.ws.requestRefundDetails(this.orderId);
    if (this.blocksUntilRefundable > 0) {
      const { network, subnet } = getNetworkDetails(this.refund.details.market);
    
      await redshift.ws.subscribeToBlockHeight(network, subnet);
      redshift.ws.onBlockHeightChanged(update =>
        this.handleBlockHeightChange(update, network, subnet),
      );
    }

    details

    This field contains the network-specific details that are necessary to submit the refund transaction. In this case, it contains two properties: to and data. These can be passed into sendTransaction in the same way as the funding details to sign and broadcast the refund transaction.

    Events

    Websocket information to communicate with REDSHIFT.

    hashtag
    Messages:

    hashtag
    requestQuote

    Use this endpoint to request a quote to exchange an on-chain asset for Lightning Bitcoin (LBTC). Available markets are in docsarrow-up-right.

    hashtag
    subscribeToOrderState

    Use this endpoint to subscribe to an order's state. If an order's state changes, subscribers will be sent a message with the new state of the order. This may be useful to notify users how their order is progressing.

    hashtag
    unsubscribeFromOrderState

    Use this endpoint to unsubscribe from an order's state. If an order's state changes in the future, the client will not be notified.

    hashtag
    subscribeToBlockHeight

    Use this endpoint to subscribe to a blockchain's current block height. If the blockchain's current block height changes, subscribers will be sent a message with the new block height. This might be useful to notify users whether they can refund their transaction.

    Valid Networks:

    • bitcoin

    • ethereum

    Valid Subnets:

    Bitcoin:

    • simnet

    • testnet

    • mainnet

    Ethereum:

    • ganache_simnet

    • kovan_testnet

    • mainnet

    hashtag
    unsubscribeFromBlockHeight

    Use this endpoint to unsubscribe from a blockchain's current block height. If the blockchain's current block height changes in the future, the client will not be notified.

    Valid Networks:

    • bitcoin

    • ethereum

    Valid Subnets:

    Bitcoin:

    • simnet

    • testnet

    • mainnet

    Ethereum:

    • ganache_simnet

    • kovan_testnet

    • mainnet

    hashtag
    requestRefundDetails

    Use this endpoint to request the refund details for a particular order. This information can also be retrieved using the requestRefundDetails HTTP endpoint, but it may make more sense to use an already established WebSocket connection depending on your application.

    hashtag
    broadcastTransaction

    Use this endpoint broadcasts a signed transaction to the specific network. This can be used to broadcast refund transactions directly from your app without needing to run a node.

    Valid onchainTickers are defined in the .

    hashtag
    requestMarketRequirements

    Use this endpoint to get the market requirements.

    N/A

    docsarrow-up-right
    // Request
    {
        market: Market;
        invoice: string;
        refundAddress?: string;
    }
    // Bitcoin Response
    {
        success: boolean,
        message: {
            orderId: string;
            expiryTimestampMs: number;
            amount: string;
            details: {
                payToAddress: string;
                redeemScript: string;
                refundableAtBlockHeight: number;
            }
        }
    }
    
    
    // Ethereum Response
    {
        success: boolean,
        message: {
            orderId: string,
            expiryTimestampMs: number,
            amount: string,
            details: {
              unsignedFundingTx: string,
            }
        }
    }
    // Request
    {
        orderId: string
    }
    // Response
    {  
        success: boolean 
    }
    import io from 'socket.io-client';
    
    const socket = io.connect("https://api.redshift.radar.tech/user/v1");
    
    const request = {
      orderId: "13283979-2c0b-45e4-b2e0-2a487535822e"
    }
    
    socket.emit(
      "subscribeToOrderState",
      request,
      ({ success, message }: WebSocketResponse<Quote | string>) => {
        if (success) {
          return resolve("Subscribed!");
        }
        return reject(new Error(message));
      },
    );
    import io from 'socket.io-client';
    
    const socket = io.connect("https://api.redshift.radar.tech/user/v1");
    
    const request = {
      market: "BTC_LBTC";
      invoice: "lnbc1m1p0pv6fepp5kjf5zgm6590w77u3qtg38cjwxwz36l505q3gurl4nl8xw47t33msdqsgdf4wfmnyp9k27tncqzpgwrdthp4apgu7v4s53xc27clm82nkchmylm5huvwdd660q36e82vske04eeesljjyt89q3aglzr9l7l3yxpy5pwfl08unztfrqhcndesqyym288";
      refundAddress: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
    }
    
    socket.emit(
      "requestQuote",
      request,
      ({ success, message }: WebSocketResponse<Quote | string>) => {
        if (success) {
          console.log(message)
          // {
          //   orderId: "13283979-2c0b-45e4-b2e0-2a487535822e";
          //   expiryTimestampMs: 1578598212267;
          //   amount: "0.001056000000000000";
          //   details: {
          //     payToAddress: "3LKomc2i3rLm2TnA36U4pGH25cFWvRMnJB";
          //     redeemScript: "76a914d5846ccb08521347c1d5718ec460656d67427cbf8763752102369b4d174f601239daef6a83bf553092e139901e6ed19ffbcbedcee07be7a4ae6703575709b17576a914c1e27ac884310271f04dd1ebe9eb4e135113a1d08868ac";
          //     refundableAtBlockHeight: 612183;
          //   }
          // }
        }
        return reject(new Error(message));
      },
    );
    // Request
    {
        orderId: string
    }
    // Response
    {  
        success: boolean 
    }
    import io from 'socket.io-client';
    
    const socket = io.connect("https://api.redshift.radar.tech/user/v1");
    
    const request = {
      orderId: "13283979-2c0b-45e4-b2e0-2a487535822e"
    }
    
    socket.emit(
      "unsubscribeFromOrderState",
      request,
      ({ success, message }: WebSocketResponse<Quote | string>) => {
        if (success) {
          return resolve("Unsubscribed!");
        }
        return reject(new Error(message));
      },
    );
    // Request
    {
        network: Network;
        subnet: Subnet;
    }
    // Response
    {  
        success: boolean 
    }
    import io from 'socket.io-client';
    
    const socket = io.connect("https://api.redshift.radar.tech/user/v1");
    
    const request = {
      network: "bitcoin";
      subnet: "mainnet";
    }
    
    socket.emit(
      "subscribeToBlockHeight",
      request,
      ({ success, message }: WebSocketResponse<Quote | string>) => {
        if (success) {
          return resolve("Subscribed!");
        }
        return reject(new Error(message));
      },
    );
    // Request
    {
        network: Network;
        subnet: Subnet;
    }
    // Response
    {  
        success: boolean 
    }
    import io from 'socket.io-client';
    
    const socket = io.connect("https://api.redshift.radar.tech/user/v1");
    
    const request = {
      network: "bitcoin";
      subnet: "mainnet";
    }
    
    socket.emit(
      "unsubscribeToBlockHeight",
      request,
      ({ success, message }: WebSocketResponse<Quote | string>) => {
        if (success) {
          return resolve("Subscribed!");
        }
        return reject(new Error(message));
      },
    );
    // Request
    {
        orderId: string
    }
    // Bitcoin Response
    {
        success: boolean,
        message: {
            market: Market;
            state: UserSwapState;
            blocksRemaining: number | undefined;
            refundableAtBlockHeight: number | undefined;
            refundableBalance: string;
            details: {
                refundAddress: string;
                redeemScript: string;
                currentBlockHeight: number;
                feeTokensPerVirtualByte: number;
                utxos: TxOutput[];
            }
        }
    }
    
    
    // Ethereum Response
    {
        success: boolean,
        message: {
            market: Market;
            state: UserSwapState;
            blocksRemaining: number | undefined;
            refundableAtBlockHeight: number | undefined;
            refundableBalance: string;
            details: {
                to: string;
                data: string;
            }
        }
    }
    import io from 'socket.io-client';
    
    const socket = io.connect("https://api.redshift.radar.tech/user/v1");
    
    const request = {
      orderId: "13283979-2c0b-45e4-b2e0-2a487535822e";
    }
    
    socket.emit(
      "requestRefundDetails",
      request,
      ({ success, message }: WebSocketResponse<Quote | string>) => {
        if (success) {
          console.log(message)
            // {
            //   market: "BTC_LBTC";
            //   state: "Completed";
            //   blocksRemaining: 0;
            //   refundableAtBlockHeight: 607878;
            //   refundableBalance: "0.0005263";
            //   details: {
            //     refundAddress: "bc1q50qmmgf3mtp95qesqq27h8gsdx9syuzv9p8qfa";
            //     redeemScript: "76a9148ec7c524f0cfca47daaa56827c76d94e0ea47c2c8763752103698deb28d5807611b0545b2c0b59ea4ae84393b2e22b7f131f1b89694b43749a6703864609b17576a914a3c1bda131dac25a03300015eb9d10698b02704c8868ac";
            //     currentBlockHeight: 612073;
            //     feeTokensPerVirtualByte: 30;
            //     utxos: [
            //       {
            //         "txId": "d2a153c4024152a06cb1ee6e5da1fac3548085ed77dc75f438586c37fed26678",
            //         "index": 1,
            //         "tokens": 52630
            //       }
            //     ];
            //   }
            // }
            
        }
        return reject(new Error(message));
      },
    );
    // Request
    {
        onchainTicker: OnChainTicker;
        signedTxHex: string;
    }
    // Response
    {
        success: boolean,
        message: {
            txId: "d2a153c4024152a06cb1ee6e5da1fac3548085ed77dc75f438586c37fed26678"
        }
    }
    import io from 'socket.io-client';
    
    const socket = io.connect("https://api.redshift.radar.tech/user/v1");
    
    const request = {
      onchainTicker: "BTC",
      signedTxHex: "8adb7454fe7d8d0f55ee89c592926d6f6f032207020e4ad56ff7ca3c80cbe26e"
    }
    
    socket.emit(
      "broadcastTransaction",
      request,
      ({ success, message }: WebSocketResponse<Quote | string>) => {
        if (success) {
          console.log(message)
            // {
            //   txId: "d2a153c4024152a06cb1ee6e5da1fac3548085ed77dc75f438586c37fed26678",
            // }
            
        }
        return reject(new Error(message));
      },
    );
    // Response
    {
        market: Market;
        payReq: {
            minExpirationSeconds: string;
            minBaseUnits: string;
            maxBaseUnits: string;
        }
    }
    import io from 'socket.io-client';
    
    const socket = io.connect("https://api.redshift.radar.tech/user/v1");
    
    const request = { }
    
    socket.emit(
      "requestMarketRequirements",
      request,
      ({ success, message }: WebSocketResponse<Quote | string>) => {
        if (success) {
          console.log(message)
            // {
            //   market: "BTC_LBTC";
            //   payReq:{
            //     minExpirationSeconds:"2400",
            //     minBaseUnits:"30000",
            //     maxBaseUnits:"1000000"
            //   }
            // }
            
        }
        return reject(new Error(message));
      },
    );