diff --git a/app/src/api/loop.ts b/app/src/api/loop.ts index 0fb5175b..3012802b 100644 --- a/app/src/api/loop.ts +++ b/app/src/api/loop.ts @@ -80,7 +80,11 @@ class LoopApi { /** * call the Loop `LoopOut` RPC and return the response */ - async loopOut(amount: number, quote: Quote): Promise { + async loopOut( + amount: number, + quote: Quote, + chanIds: number[], + ): Promise { const req = new LOOP.LoopOutRequest(); req.setAmt(amount); req.setMaxSwapFee(quote.swapFee); @@ -88,6 +92,7 @@ class LoopApi { req.setMaxPrepayAmt(quote.prepayAmount); req.setMaxSwapRoutingFee(this._calcRoutingFee(quote.swapFee)); req.setMaxPrepayRoutingFee(this._calcRoutingFee(quote.prepayAmount)); + req.setOutgoingChanSetList(chanIds); if (IS_PROD) { // in prod env, push the deadline out for 30 mins for lower fees diff --git a/app/src/store/stores/buildSwapStore.ts b/app/src/store/stores/buildSwapStore.ts index f8030a55..1b0304e3 100644 --- a/app/src/store/stores/buildSwapStore.ts +++ b/app/src/store/stores/buildSwapStore.ts @@ -296,10 +296,11 @@ class BuildSwapStore { ); this.processingTimeout = setTimeout(async () => { try { + const chanIds = this.selectedChanIds.map(v => parseInt(v)); const res = direction === SwapDirection.IN ? await this._store.api.loop.loopIn(amount, quote) - : await this._store.api.loop.loopOut(amount, quote); + : await this._store.api.loop.loopOut(amount, quote, chanIds); this._store.log.info('completed loop', toJS(res)); runInAction('requestSwapContinuation', () => { // hide the swap UI after it is complete diff --git a/app/src/types/generated/loop_pb.d.ts b/app/src/types/generated/loop_pb.d.ts index abcc9728..6e6d502c 100644 --- a/app/src/types/generated/loop_pb.d.ts +++ b/app/src/types/generated/loop_pb.d.ts @@ -29,6 +29,11 @@ export class LoopOutRequest extends jspb.Message { getLoopOutChannel(): number; setLoopOutChannel(value: number): void; + clearOutgoingChanSetList(): void; + getOutgoingChanSetList(): Array; + setOutgoingChanSetList(value: Array): void; + addOutgoingChanSet(value: number, index?: number): number; + getSweepConfTarget(): number; setSweepConfTarget(value: number): void; @@ -55,6 +60,7 @@ export namespace LoopOutRequest { maxPrepayAmt: number, maxMinerFee: number, loopOutChannel: number, + outgoingChanSetList: Array, sweepConfTarget: number, swapPublicationDeadline: number, } @@ -78,6 +84,9 @@ export class LoopInRequest extends jspb.Message { getExternalHtlc(): boolean; setExternalHtlc(value: boolean): void; + getHtlcConfTarget(): number; + setHtlcConfTarget(value: number): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LoopInRequest.AsObject; static toObject(includeInstance: boolean, msg: LoopInRequest): LoopInRequest.AsObject; @@ -95,6 +104,7 @@ export namespace LoopInRequest { maxMinerFee: number, lastHop: Uint8Array | string, externalHtlc: boolean, + htlcConfTarget: number, } } @@ -110,6 +120,12 @@ export class SwapResponse extends jspb.Message { getHtlcAddress(): string; setHtlcAddress(value: string): void; + getHtlcAddressNp2wsh(): string; + setHtlcAddressNp2wsh(value: string): void; + + getHtlcAddressP2wsh(): string; + setHtlcAddressP2wsh(value: string): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): SwapResponse.AsObject; static toObject(includeInstance: boolean, msg: SwapResponse): SwapResponse.AsObject; @@ -125,6 +141,8 @@ export namespace SwapResponse { id: string, idBytes: Uint8Array | string, htlcAddress: string, + htlcAddressNp2wsh: string, + htlcAddressP2wsh: string, } } @@ -171,6 +189,12 @@ export class SwapStatus extends jspb.Message { getHtlcAddress(): string; setHtlcAddress(value: string): void; + getHtlcAddressP2wsh(): string; + setHtlcAddressP2wsh(value: string): void; + + getHtlcAddressNp2wsh(): string; + setHtlcAddressNp2wsh(value: string): void; + getCostServer(): number; setCostServer(value: number): void; @@ -200,6 +224,8 @@ export namespace SwapStatus { initiationTime: number, lastUpdateTime: number, htlcAddress: string, + htlcAddressP2wsh: string, + htlcAddressNp2wsh: string, costServer: number, costOnchain: number, costOffchain: number, diff --git a/app/src/types/generated/loop_pb.js b/app/src/types/generated/loop_pb.js index 89e62185..7e90391b 100644 --- a/app/src/types/generated/loop_pb.js +++ b/app/src/types/generated/loop_pb.js @@ -45,7 +45,7 @@ goog.exportSymbol('proto.looprpc.TokensResponse', null, global); * @constructor */ proto.looprpc.LoopOutRequest = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); + jspb.Message.initialize(this, opt_data, 0, -1, proto.looprpc.LoopOutRequest.repeatedFields_, null); }; goog.inherits(proto.looprpc.LoopOutRequest, jspb.Message); if (goog.DEBUG && !COMPILED) { @@ -350,6 +350,13 @@ if (goog.DEBUG && !COMPILED) { proto.looprpc.LsatToken.displayName = 'proto.looprpc.LsatToken'; } +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.looprpc.LoopOutRequest.repeatedFields_ = [11]; + if (jspb.Message.GENERATE_TO_OBJECT) { @@ -389,6 +396,7 @@ proto.looprpc.LoopOutRequest.toObject = function(includeInstance, msg) { maxPrepayAmt: jspb.Message.getFieldWithDefault(msg, 6, 0), maxMinerFee: jspb.Message.getFieldWithDefault(msg, 7, 0), loopOutChannel: jspb.Message.getFieldWithDefault(msg, 8, 0), + outgoingChanSetList: (f = jspb.Message.getRepeatedField(msg, 11)) == null ? undefined : f, sweepConfTarget: jspb.Message.getFieldWithDefault(msg, 9, 0), swapPublicationDeadline: jspb.Message.getFieldWithDefault(msg, 10, 0) }; @@ -459,6 +467,10 @@ proto.looprpc.LoopOutRequest.deserializeBinaryFromReader = function(msg, reader) var value = /** @type {number} */ (reader.readUint64()); msg.setLoopOutChannel(value); break; + case 11: + var value = /** @type {!Array} */ (reader.readPackedUint64()); + msg.setOutgoingChanSetList(value); + break; case 9: var value = /** @type {number} */ (reader.readInt32()); msg.setSweepConfTarget(value); @@ -552,6 +564,13 @@ proto.looprpc.LoopOutRequest.serializeBinaryToWriter = function(message, writer) f ); } + f = message.getOutgoingChanSetList(); + if (f.length > 0) { + writer.writePackedUint64( + 11, + f + ); + } f = message.getSweepConfTarget(); if (f !== 0) { writer.writeInt32( @@ -713,6 +732,43 @@ proto.looprpc.LoopOutRequest.prototype.setLoopOutChannel = function(value) { }; +/** + * repeated uint64 outgoing_chan_set = 11; + * @return {!Array} + */ +proto.looprpc.LoopOutRequest.prototype.getOutgoingChanSetList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 11)); +}; + + +/** + * @param {!Array} value + * @return {!proto.looprpc.LoopOutRequest} returns this + */ +proto.looprpc.LoopOutRequest.prototype.setOutgoingChanSetList = function(value) { + return jspb.Message.setField(this, 11, value || []); +}; + + +/** + * @param {number} value + * @param {number=} opt_index + * @return {!proto.looprpc.LoopOutRequest} returns this + */ +proto.looprpc.LoopOutRequest.prototype.addOutgoingChanSet = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 11, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.looprpc.LoopOutRequest} returns this + */ +proto.looprpc.LoopOutRequest.prototype.clearOutgoingChanSetList = function() { + return this.setOutgoingChanSetList([]); +}; + + /** * optional int32 sweep_conf_target = 9; * @return {number} @@ -785,7 +841,8 @@ proto.looprpc.LoopInRequest.toObject = function(includeInstance, msg) { maxSwapFee: jspb.Message.getFieldWithDefault(msg, 2, 0), maxMinerFee: jspb.Message.getFieldWithDefault(msg, 3, 0), lastHop: msg.getLastHop_asB64(), - externalHtlc: jspb.Message.getBooleanFieldWithDefault(msg, 5, false) + externalHtlc: jspb.Message.getBooleanFieldWithDefault(msg, 5, false), + htlcConfTarget: jspb.Message.getFieldWithDefault(msg, 6, 0) }; if (includeInstance) { @@ -842,6 +899,10 @@ proto.looprpc.LoopInRequest.deserializeBinaryFromReader = function(msg, reader) var value = /** @type {boolean} */ (reader.readBool()); msg.setExternalHtlc(value); break; + case 6: + var value = /** @type {number} */ (reader.readInt32()); + msg.setHtlcConfTarget(value); + break; default: reader.skipField(); break; @@ -906,6 +967,13 @@ proto.looprpc.LoopInRequest.serializeBinaryToWriter = function(message, writer) f ); } + f = message.getHtlcConfTarget(); + if (f !== 0) { + writer.writeInt32( + 6, + f + ); + } }; @@ -1023,6 +1091,24 @@ proto.looprpc.LoopInRequest.prototype.setExternalHtlc = function(value) { }; +/** + * optional int32 htlc_conf_target = 6; + * @return {number} + */ +proto.looprpc.LoopInRequest.prototype.getHtlcConfTarget = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 6, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.looprpc.LoopInRequest} returns this + */ +proto.looprpc.LoopInRequest.prototype.setHtlcConfTarget = function(value) { + return jspb.Message.setProto3IntField(this, 6, value); +}; + + @@ -1057,7 +1143,9 @@ proto.looprpc.SwapResponse.toObject = function(includeInstance, msg) { var f, obj = { id: jspb.Message.getFieldWithDefault(msg, 1, ""), idBytes: msg.getIdBytes_asB64(), - htlcAddress: jspb.Message.getFieldWithDefault(msg, 2, "") + htlcAddress: jspb.Message.getFieldWithDefault(msg, 2, ""), + htlcAddressNp2wsh: jspb.Message.getFieldWithDefault(msg, 4, ""), + htlcAddressP2wsh: jspb.Message.getFieldWithDefault(msg, 5, "") }; if (includeInstance) { @@ -1106,6 +1194,14 @@ proto.looprpc.SwapResponse.deserializeBinaryFromReader = function(msg, reader) { var value = /** @type {string} */ (reader.readString()); msg.setHtlcAddress(value); break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setHtlcAddressNp2wsh(value); + break; + case 5: + var value = /** @type {string} */ (reader.readString()); + msg.setHtlcAddressP2wsh(value); + break; default: reader.skipField(); break; @@ -1156,6 +1252,20 @@ proto.looprpc.SwapResponse.serializeBinaryToWriter = function(message, writer) { f ); } + f = message.getHtlcAddressNp2wsh(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } + f = message.getHtlcAddressP2wsh(); + if (f.length > 0) { + writer.writeString( + 5, + f + ); + } }; @@ -1237,6 +1347,42 @@ proto.looprpc.SwapResponse.prototype.setHtlcAddress = function(value) { }; +/** + * optional string htlc_address_np2wsh = 4; + * @return {string} + */ +proto.looprpc.SwapResponse.prototype.getHtlcAddressNp2wsh = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.looprpc.SwapResponse} returns this + */ +proto.looprpc.SwapResponse.prototype.setHtlcAddressNp2wsh = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + +/** + * optional string htlc_address_p2wsh = 5; + * @return {string} + */ +proto.looprpc.SwapResponse.prototype.getHtlcAddressP2wsh = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 5, "")); +}; + + +/** + * @param {string} value + * @return {!proto.looprpc.SwapResponse} returns this + */ +proto.looprpc.SwapResponse.prototype.setHtlcAddressP2wsh = function(value) { + return jspb.Message.setProto3StringField(this, 5, value); +}; + + @@ -1378,6 +1524,8 @@ proto.looprpc.SwapStatus.toObject = function(includeInstance, msg) { initiationTime: jspb.Message.getFieldWithDefault(msg, 5, 0), lastUpdateTime: jspb.Message.getFieldWithDefault(msg, 6, 0), htlcAddress: jspb.Message.getFieldWithDefault(msg, 7, ""), + htlcAddressP2wsh: jspb.Message.getFieldWithDefault(msg, 12, ""), + htlcAddressNp2wsh: jspb.Message.getFieldWithDefault(msg, 13, ""), costServer: jspb.Message.getFieldWithDefault(msg, 8, 0), costOnchain: jspb.Message.getFieldWithDefault(msg, 9, 0), costOffchain: jspb.Message.getFieldWithDefault(msg, 10, 0) @@ -1449,6 +1597,14 @@ proto.looprpc.SwapStatus.deserializeBinaryFromReader = function(msg, reader) { var value = /** @type {string} */ (reader.readString()); msg.setHtlcAddress(value); break; + case 12: + var value = /** @type {string} */ (reader.readString()); + msg.setHtlcAddressP2wsh(value); + break; + case 13: + var value = /** @type {string} */ (reader.readString()); + msg.setHtlcAddressNp2wsh(value); + break; case 8: var value = /** @type {number} */ (reader.readInt64()); msg.setCostServer(value); @@ -1546,6 +1702,20 @@ proto.looprpc.SwapStatus.serializeBinaryToWriter = function(message, writer) { f ); } + f = message.getHtlcAddressP2wsh(); + if (f.length > 0) { + writer.writeString( + 12, + f + ); + } + f = message.getHtlcAddressNp2wsh(); + if (f.length > 0) { + writer.writeString( + 13, + f + ); + } f = message.getCostServer(); if (f !== 0) { writer.writeInt64( @@ -1738,6 +1908,42 @@ proto.looprpc.SwapStatus.prototype.setHtlcAddress = function(value) { }; +/** + * optional string htlc_address_p2wsh = 12; + * @return {string} + */ +proto.looprpc.SwapStatus.prototype.getHtlcAddressP2wsh = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 12, "")); +}; + + +/** + * @param {string} value + * @return {!proto.looprpc.SwapStatus} returns this + */ +proto.looprpc.SwapStatus.prototype.setHtlcAddressP2wsh = function(value) { + return jspb.Message.setProto3StringField(this, 12, value); +}; + + +/** + * optional string htlc_address_np2wsh = 13; + * @return {string} + */ +proto.looprpc.SwapStatus.prototype.getHtlcAddressNp2wsh = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 13, "")); +}; + + +/** + * @param {string} value + * @return {!proto.looprpc.SwapStatus} returns this + */ +proto.looprpc.SwapStatus.prototype.setHtlcAddressNp2wsh = function(value) { + return jspb.Message.setProto3StringField(this, 13, value); +}; + + /** * optional int64 cost_server = 8; * @return {number} diff --git a/app/src/util/tests/sampleData.ts b/app/src/util/tests/sampleData.ts index 4413c37a..e6bb2d87 100644 --- a/app/src/util/tests/sampleData.ts +++ b/app/src/util/tests/sampleData.ts @@ -119,6 +119,8 @@ export const loopListSwaps: LOOP.ListSwapsResponse.AsObject = { initiationTime: 1586390353623905000 + i * 100000000000000, lastUpdateTime: 1586398369729857000 + i * 200000000000000, htlcAddress: 'bcrt1qzu4077erkr78k52yuf2rwkk6ayr6m3wtazdfz2qqmd7taa5vvy9s5d75gd', + htlcAddressP2wsh: 'bcrt1qzu4077erkr78k52yuf2rwkk6ayr6m3wtazdfz2qqmd7taa5vvy9s5d75gd', + htlcAddressNp2wsh: '', costServer: 66, costOnchain: 6812, costOffchain: 2, diff --git a/proto/loop.proto b/proto/loop.proto index f7e53e89..76c23d52 100644 --- a/proto/loop.proto +++ b/proto/loop.proto @@ -163,10 +163,18 @@ message LoopOutRequest { int64 max_miner_fee = 7; /** - The channel to loop out, the channel to loop out is selected based on the - lowest routing fee for the swap payment to the server. + Deprecated, use outgoing_chan_set. The channel to loop out, the channel + to loop out is selected based on the lowest routing fee for the swap + payment to the server. */ - uint64 loop_out_channel = 8; + uint64 loop_out_channel = 8 [deprecated = true]; + + /** + A restriction on the channel set that may be used to loop out. The actual + channel(s) that will be used are selected based on the lowest routing fee + for the swap payment to the server. + */ + repeated uint64 outgoing_chan_set = 11; /** The number of blocks from the on-chain HTLC's confirmation height that it @@ -219,6 +227,11 @@ message LoopInRequest { actor. */ bool external_htlc = 5; + + /** + The number of blocks that the on chain htlc should confirm within. + */ + int32 htlc_conf_target = 6; } message SwapResponse { @@ -237,9 +250,28 @@ message SwapResponse { bytes id_bytes = 3; /** - The address of the on-chain htlc. + DEPRECATED. This field stores the address of the onchain htlc, but + depending on the request, the semantics are different. + - For internal loop-in htlc_address contains the address of the + native segwit (P2WSH) htlc. + - For external loop-in htlc_address contains the address of the + nested segwit (NP2WSH) htlc. + - For loop-out htlc_address always contains the native segwit (P2WSH) + htlc address. */ - string htlc_address = 2; + string htlc_address = 2 [deprecated = true]; + + /** + The nested segwit address of the on-chain htlc. + This field remains empty for loop-out. + */ + string htlc_address_np2wsh = 4; + + /** + The native segwit address of the on-chain htlc. + Used for both loop-in and loop-out. + */ + string htlc_address_p2wsh = 5; } message MonitorRequest { @@ -287,9 +319,21 @@ message SwapStatus { int64 last_update_time = 6; /** - Htlc address. + DEPRECATED: This field stores the address of the onchain htlc. + - For internal loop-in htlc_address contains the address of the + native segwit (P2WSH) htlc. + - For external loop-in htlc_address contains the nested segwit (NP2WSH) + address. + - For loop-out htlc_address always contains the native segwit (P2WSH) + htlc address. */ - string htlc_address = 7; + string htlc_address = 7 [deprecated = true]; + + // HTLC address (native segwit), used in loop-in and loop-out swaps. + string htlc_address_p2wsh = 12; + + // HTLC address (nested segwit), used in loop-in swaps only. + string htlc_address_np2wsh = 13; /// Swap server cost int64 cost_server = 8;