diff --git a/config.go b/config.go index 68d78e58d..208565812 100644 --- a/config.go +++ b/config.go @@ -211,6 +211,8 @@ type Config struct { PriceOracle rfq.PriceOracle + PriceOracleSendPeerID bool + UniverseStats universe.Telemetry AuxLeafSigner *tapchannel.AuxLeafSigner diff --git a/docs/release-notes/release-notes-0.7.0.md b/docs/release-notes/release-notes-0.7.0.md index 5ab6dac24..99a1b9573 100644 --- a/docs/release-notes/release-notes-0.7.0.md +++ b/docs/release-notes/release-notes-0.7.0.md @@ -81,6 +81,15 @@ tag and also needs to be toggled on via the `channel.noop-htlcs` configuration option. +- [Two new configuration values were added to improve privacy when using public + or untrusted third-party price + oracles](https://github.com/lightninglabs/taproot-assets/pull/1677): + `experimental.rfq.sendpricehint` controls whether a price hint is queried + from the local price oracle and sent to the peer when requesting a price + quote (opt-in, default `false`). `experimental.rfq.priceoraclesendpeerid` + controls whether the peer's identity public key is sent to the local price + oracle when querying asset price rates. + ## RPC Additions - The [price oracle RPC calls now have an intent, optional peer ID and metadata diff --git a/docs/rfq-and-decimal-display.md b/docs/rfq-and-decimal-display.md index 71a15e4cd..e54fe4e32 100644 --- a/docs/rfq-and-decimal-display.md +++ b/docs/rfq-and-decimal-display.md @@ -208,6 +208,42 @@ convert from mSAT to asset units as follows: `price_in_asset` * `Y` is the number of asset units per BTC, specified by `price_out_asset` +### Price oracle interaction + +```mermaid +sequenceDiagram + actor User + box Seller (user) + participant NodeA as Node A + participant OracleA as Price Oracle A + end + box Buyer (edge node) + participant NodeB as Node B + participant OracleB as Price Oracle B + end + + User->>+NodeA: SendPayment + + NodeA->>+NodeA: AddAssetSellOrder + + NodeA->>+OracleA: Get price rate hint
(QueryAssetRate[type=SALE,intent=PAY_INVOICE_HINT,peer=NodeB]) + OracleA-->>-NodeA: Price rate hint + + NodeA->>+NodeB: Send sell request with price
rate hint over p2p + + NodeB->>+OracleB: Determine actual price
rate using suggested price
(QueryAssetRate[type=PURCHASE,intent=PAY_INVOICE,peer=NodeA]) + OracleB-->>-NodeB: Actual price rate + + NodeB-->>-NodeA: Return actual price rate + + NodeA->>+OracleA: Validate actual price rate
(QueryAssetRate[type=SALE,intent=PAY_INVOICE_QUALIFY,peer=NodeB]) + OracleA-->>-NodeA: Approve actual price rate + + NodeA->>-NodeA: Send payment over LN using approved actual price rate + + NodeA->>-User: Payment result +``` + ## Buy Order (Receiving via an Invoice) The buy order covers the second user story: The user wants to get paid, they @@ -245,6 +281,43 @@ node as: * `M` is the number of mSAT in a BTC (100,000,000,000), specified by `price_in_asset` +### Price oracle interaction + +```mermaid +sequenceDiagram + actor User + box Buyer (user) + participant NodeA as Node A + participant OracleA as Price Oracle A + end + box Seller (edge node) + participant NodeB as Node B + participant OracleB as Price Oracle B + end + + User->>+NodeA: AddInvoice + + NodeA->>+NodeA: AddAssetBuyOrder + + NodeA->>+OracleA: Get price rate hint
(QueryAssetRate[type=PURCHASE,intent=RECV_PAYMENT_HINT,peer=NodeB]) + OracleA-->>-NodeA: Price rate hint + + NodeA->>+NodeB: Send buy request with price
rate hint over p2p + + NodeB->>+OracleB: Determine actual price
rate using suggested price
(QueryAssetRate[type=SALE,intent=RECV_PAYMENT,peer=NodeA]) + OracleB-->>-NodeB: Actual price rate + + NodeB-->>-NodeA: Return actual price rate + + NodeA->>+OracleA: Validate actual price rate
(QueryAssetRate[type=PURCHASE,intent=RECV_PAYMENT_QUALIFY,peer=NodeB]) + OracleA-->>-NodeA: Approve actual price rate + + NodeA->>-NodeA: Create invoice using actual price rate + + NodeA->>-User: Invoice +``` + + ## Examples See `TestFindDecimalDisplayBoundaries` and `TestUsdToJpy` in diff --git a/itest/oracle_harness.go b/itest/oracle_harness.go index ca854f459..2252bca33 100644 --- a/itest/oracle_harness.go +++ b/itest/oracle_harness.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "net" + "slices" "testing" "time" @@ -16,6 +17,7 @@ import ( "github.com/lightninglabs/taproot-assets/rpcutils" oraclerpc "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" "github.com/lightningnetwork/lnd/cert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -30,17 +32,25 @@ type oracleHarness struct { grpcListener net.Listener grpcServer *grpc.Server - // bidPrices is a map used internally by the oracle harness to store bid + // Mock is a mock object that can optionally be used to track calls to + // the oracle harness. If no call expectations are set, the prices from + // the maps below will be used. + // NOTE: When setting up the call expectations, we need to use the + // actual fields of the `QueryAssetRatesRequest` message, since + // otherwise it is much harder to match the calls nicely. + mock.Mock + + // buyPrices is a map used internally by the oracle harness to store buy // prices for certain assets. We use the asset specifier string as a // unique identifier, since it will either contain an asset ID or a // group key. - bidPrices map[string]rfqmath.BigIntFixedPoint + buyPrices map[string]rfqmath.BigIntFixedPoint - // askPrices is a map used internally by the oracle harness to store ask - // prices for certain assets. We use the asset specifier string as a - // unique identifier, since it will either contain an asset ID or a + // sellPrices is a map used internally by the oracle harness to store + // sell prices for certain assets. We use the asset specifier string as + // a unique identifier, since it will either contain an asset ID or a // group key. - askPrices map[string]rfqmath.BigIntFixedPoint + sellPrices map[string]rfqmath.BigIntFixedPoint } // newOracleHarness returns a new oracle harness instance that is set to listen @@ -48,17 +58,17 @@ type oracleHarness struct { func newOracleHarness(listenAddr string) *oracleHarness { return &oracleHarness{ listenAddr: listenAddr, - bidPrices: make(map[string]rfqmath.BigIntFixedPoint), - askPrices: make(map[string]rfqmath.BigIntFixedPoint), + buyPrices: make(map[string]rfqmath.BigIntFixedPoint), + sellPrices: make(map[string]rfqmath.BigIntFixedPoint), } } -// setPrice sets the target bid and ask price for the provided specifier. -func (o *oracleHarness) setPrice(specifier asset.Specifier, bidPrice, - askPrice rfqmath.BigIntFixedPoint) { +// setPrice sets the target buy and sell price for the provided specifier. +func (o *oracleHarness) setPrice(specifier asset.Specifier, buyPrice, + sellPrice rfqmath.BigIntFixedPoint) { - o.bidPrices[specifier.String()] = bidPrice - o.askPrices[specifier.String()] = askPrice + o.buyPrices[specifier.String()] = buyPrice + o.sellPrices[specifier.String()] = sellPrice } // start runs the oracle harness. @@ -113,14 +123,14 @@ func (o *oracleHarness) getAssetRates(specifier asset.Specifier, // Determine the rate based on the transaction type. var subjectAssetRate rfqmath.BigIntFixedPoint if transactionType == oraclerpc.TransactionType_PURCHASE { - rate, ok := o.bidPrices[specifier.String()] + rate, ok := o.buyPrices[specifier.String()] if !ok { return oraclerpc.AssetRates{}, fmt.Errorf("purchase "+ "price not found for %s", specifier.String()) } subjectAssetRate = rate } else { - rate, ok := o.askPrices[specifier.String()] + rate, ok := o.sellPrices[specifier.String()] if !ok { return oraclerpc.AssetRates{}, fmt.Errorf("sale "+ "price not found for %s", specifier.String()) @@ -181,6 +191,18 @@ func (o *oracleHarness) QueryAssetRates(_ context.Context, req *oraclerpc.QueryAssetRatesRequest) ( *oraclerpc.QueryAssetRatesResponse, error) { + // Return early with the mocked value if call expectations are set up. + if hasExpectedCall(o.ExpectedCalls, "QueryAssetRates") { + args := o.Called( + req.TransactionType, req.SubjectAsset, + req.SubjectAssetMaxAmount, req.PaymentAsset, + req.PaymentAssetMaxAmount, req.AssetRatesHint, + req.Intent, req.CounterpartyId, req.Metadata, + ) + resp, _ := args.Get(0).(*oraclerpc.QueryAssetRatesResponse) + return resp, args.Error(1) + } + // Ensure that the payment asset is BTC. We only support BTC as the // payment asset in this example. if !rpcutils.IsAssetBtc(req.PaymentAsset) { @@ -203,8 +225,8 @@ func (o *oracleHarness) QueryAssetRates(_ context.Context, return nil, fmt.Errorf("error parsing subject asset: %w", err) } - _, hasPurchase := o.bidPrices[specifier.String()] - _, hasSale := o.askPrices[specifier.String()] + _, hasPurchase := o.buyPrices[specifier.String()] + _, hasSale := o.sellPrices[specifier.String()] log.Infof("Have for %s, purchase=%v, sale=%v", specifier.String(), hasPurchase, hasSale) @@ -324,3 +346,11 @@ func generateSelfSignedCert() (tls.Certificate, error) { return tlsCert, nil } + +// hasExpectedCall checks if the method call has been registered as an expected +// call with the mock object. +func hasExpectedCall(expectedCalls []*mock.Call, method string) bool { + return slices.ContainsFunc(expectedCalls, func(call *mock.Call) bool { + return call.Method == method + }) +} diff --git a/itest/rfq_test.go b/itest/rfq_test.go index 46cbb7839..340551f19 100644 --- a/itest/rfq_test.go +++ b/itest/rfq_test.go @@ -1,6 +1,7 @@ package itest import ( + "bytes" "context" "fmt" "math" @@ -15,6 +16,7 @@ import ( "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightninglabs/taproot-assets/rfqmsg" "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" + oraclerpc "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" @@ -25,6 +27,7 @@ import ( "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -49,8 +52,17 @@ var ( // As a final step (which is not part of this test), Bob's node will transfer // the tap asset to Carol's node. func testRfqAssetBuyHtlcIntercept(t *harnessTest) { + // For this test we'll use an actual oracle RPC server harness. + oracleAddr := fmt.Sprintf("localhost:%d", port.NextAvailablePort()) + oracle := newOracleHarness(oracleAddr) + oracle.start(t.t) + t.t.Cleanup(oracle.stop) + + // We need to craft the oracle server URL in the correct format. + oracleURL := fmt.Sprintf("rfqrpc://%s", oracleAddr) + // Initialize a new test scenario. - ts := newRfqTestScenario(t) + ts := newRfqTestScenario(t, WithRfqOracleServer(oracleURL)) // Mint an asset with Bob's tapd node. rpcAssets := MintAssetsConfirmBatch( @@ -108,8 +120,74 @@ func testRfqAssetBuyHtlcIntercept(t *harnessTest) { // node. PeerPubKey: ts.BobLnd.PubKey[:], - TimeoutSeconds: uint32(rfqTimeout.Seconds()), + TimeoutSeconds: uint32(rfqTimeout.Seconds()), + PriceOracleMetadata: "buy-order-1", } + + // We now set up the expected calls that should come in to the mock + // oracle server during the process. + buySpecifier := &oraclerpc.AssetSpecifier{ + Id: &oraclerpc.AssetSpecifier_AssetId{ + AssetId: mintedAssetId, + }, + } + btcSpecifier := &oraclerpc.AssetSpecifier{ + Id: &oraclerpc.AssetSpecifier_AssetId{ + AssetId: bytes.Repeat([]byte{0}, 32), + }, + } + mockResult := &oraclerpc.QueryAssetRatesResponse{ + Result: &oraclerpc.QueryAssetRatesResponse_Ok{ + Ok: &oraclerpc.QueryAssetRatesOkResponse{ + AssetRates: &oraclerpc.AssetRates{ + SubjectAssetRate: &oraclerpc.FixedPoint{ + Coefficient: "1000000", + Scale: 3, + }, + ExpiryTimestamp: uint64( + time.Now().Add(time.Minute). + Unix(), + ), + }, + }, + }, + } + + // The first call is expected to be the rate hint call for the local + // oracle, using the hint intent. + oracle.On( + "QueryAssetRates", oraclerpc.TransactionType_PURCHASE, + buySpecifier, purchaseAssetAmt, btcSpecifier, + mock.Anything, mock.Anything, + oraclerpc.Intent_INTENT_RECV_PAYMENT_HINT, + mock.Anything, "buy-order-1", + ).Return(mockResult, nil).Once() + + // Then the counterparty will do a sale call with the intent to receive + // a payment. + oracle.On( + "QueryAssetRates", oraclerpc.TransactionType_SALE, + buySpecifier, purchaseAssetAmt, btcSpecifier, + mock.Anything, mock.Anything, + oraclerpc.Intent_INTENT_RECV_PAYMENT, + mock.Anything, "buy-order-1", + ).Return(mockResult, nil).Once() + + // And finally, we'll qualify (validate) the price again on our end. + oracle.On( + "QueryAssetRates", oraclerpc.TransactionType_PURCHASE, + buySpecifier, purchaseAssetAmt, btcSpecifier, + mock.Anything, mock.Anything, + oraclerpc.Intent_INTENT_RECV_PAYMENT_QUALIFY, + mock.Anything, "buy-order-1", + ).Return(mockResult, nil).Once() + + // At the end of the test, we also want to make sure each call came in + // as expected. + defer func() { + oracle.AssertExpectations(t.t) + }() + _, err = ts.AliceTapd.AddAssetBuyOrder(ctxt, buyReq) require.ErrorContains( t.t, err, "error checking peer channel: error checking asset "+ @@ -236,8 +314,17 @@ func testRfqAssetBuyHtlcIntercept(t *harnessTest) { // validation between three peers. The RFQ negotiation is initiated by an asset // sell request. func testRfqAssetSellHtlcIntercept(t *harnessTest) { + // For this test we'll use an actual oracle RPC server harness. + oracleAddr := fmt.Sprintf("localhost:%d", port.NextAvailablePort()) + oracle := newOracleHarness(oracleAddr) + oracle.start(t.t) + t.t.Cleanup(oracle.stop) + + // We need to craft the oracle server URL in the correct format. + oracleURL := fmt.Sprintf("rfqrpc://%s", oracleAddr) + // Initialize a new test scenario. - ts := newRfqTestScenario(t) + ts := newRfqTestScenario(t, WithRfqOracleServer(oracleURL)) // Mint an asset with Alice's tapd node. rpcAssets := MintAssetsConfirmBatch( @@ -297,8 +384,74 @@ func testRfqAssetSellHtlcIntercept(t *harnessTest) { // Bob's node. PeerPubKey: ts.BobLnd.PubKey[:], - TimeoutSeconds: uint32(rfqTimeout.Seconds()), + TimeoutSeconds: uint32(rfqTimeout.Seconds()), + PriceOracleMetadata: "sell-order-1", + } + + // We now set up the expected calls that should come in to the mock + // oracle server during the process. + buySpecifier := &oraclerpc.AssetSpecifier{ + Id: &oraclerpc.AssetSpecifier_AssetId{ + AssetId: mintedAssetIdBytes, + }, } + btcSpecifier := &oraclerpc.AssetSpecifier{ + Id: &oraclerpc.AssetSpecifier_AssetId{ + AssetId: bytes.Repeat([]byte{0}, 32), + }, + } + mockResult := &oraclerpc.QueryAssetRatesResponse{ + Result: &oraclerpc.QueryAssetRatesResponse_Ok{ + Ok: &oraclerpc.QueryAssetRatesOkResponse{ + AssetRates: &oraclerpc.AssetRates{ + SubjectAssetRate: &oraclerpc.FixedPoint{ + Coefficient: "1000000", + Scale: 3, + }, + ExpiryTimestamp: uint64( + time.Now().Add(time.Minute). + Unix(), + ), + }, + }, + }, + } + + // The first call is expected to be the rate hint call for the local + // oracle, using the hint intent. + oracle.On( + "QueryAssetRates", oraclerpc.TransactionType_SALE, + buySpecifier, mock.Anything, btcSpecifier, + askAmt, mock.Anything, + oraclerpc.Intent_INTENT_PAY_INVOICE_HINT, + mock.Anything, "sell-order-1", + ).Return(mockResult, nil).Once() + + // Then the counterparty will do a sale call with the intent to receive + // a payment. + oracle.On( + "QueryAssetRates", oraclerpc.TransactionType_PURCHASE, + buySpecifier, mock.Anything, btcSpecifier, + askAmt, mock.Anything, + oraclerpc.Intent_INTENT_PAY_INVOICE, + mock.Anything, "sell-order-1", + ).Return(mockResult, nil).Once() + + // And finally, we'll qualify (validate) the price again on our end. + oracle.On( + "QueryAssetRates", oraclerpc.TransactionType_SALE, + buySpecifier, mock.Anything, btcSpecifier, + askAmt, mock.Anything, + oraclerpc.Intent_INTENT_PAY_INVOICE_QUALIFY, + mock.Anything, "sell-order-1", + ).Return(mockResult, nil).Once() + + // At the end of the test, we also want to make sure each call came in + // as expected. + defer func() { + oracle.AssertExpectations(t.t) + }() + _, err = ts.AliceTapd.AddAssetSellOrder(ctxt, sellReq) require.ErrorContains( t.t, err, "error checking peer channel: error checking asset "+ @@ -690,18 +843,18 @@ func newRfqTestScenario(t *harnessTest, opts ...RfqOption) *rfqTestScenario { aliceTapd := setupTapdHarness( t.t, t, aliceLnd, t.universeServer, WithOracleServer( rfqOpts.oracleServerAddr, rfqOpts.oracleServerAlice, - ), + ), WithSendPriceHint(), ) bobTapd := setupTapdHarness( t.t, t, bobLnd, t.universeServer, WithOracleServer( rfqOpts.oracleServerAddr, rfqOpts.oracleServerBob, - ), + ), WithSendPriceHint(), ) carolTapd := setupTapdHarness( t.t, t, carolLnd, t.universeServer, WithOracleServer( rfqOpts.oracleServerAddr, rfqOpts.oracleServerCarol, - ), + ), WithSendPriceHint(), ) ts := rfqTestScenario{ diff --git a/itest/tapd_harness.go b/itest/tapd_harness.go index 786fb77e6..1c4e4d620 100644 --- a/itest/tapd_harness.go +++ b/itest/tapd_harness.go @@ -134,6 +134,10 @@ type harnessOpts struct { // disableSyncCache is a flag that can be set to true to disable the // universe syncer cache. disableSyncCache bool + + // sendPriceHint indicates whether the tapd should send price hints from + // the local oracle to the counterparty when requesting a quote. + sendPriceHint bool } type harnessOption func(*harnessOpts) @@ -316,6 +320,10 @@ func newTapdHarness(t *testing.T, ht *harnessTest, cfg tapdConfig, finalCfg.Universe.MultiverseCaches.SyncerCacheEnabled = true } + if opts.sendPriceHint { + finalCfg.Experimental.Rfq.SendPriceHint = true + } + return &tapdHarness{ cfg: &cfg, clientCfg: finalCfg, diff --git a/itest/test_harness.go b/itest/test_harness.go index d3bba89d6..9e5559a5f 100644 --- a/itest/test_harness.go +++ b/itest/test_harness.go @@ -368,6 +368,10 @@ type tapdHarnessParams struct { // oracleServerAddress defines the oracle server's address that this // tapd harness is going to use. oracleServerAddress string + + // sendPriceHint indicates whether the tapd should send price hints from + // the local oracle to the counterparty when requesting a quote. + sendPriceHint bool } // Option is a tapd harness option. @@ -387,6 +391,15 @@ func WithOracleServer(global, override string) Option { } } +// WithSendPriceHint is a functional option that indicates that the tapd node +// should send price hints from the local oracle to the counterparty when +// requesting a quote. +func WithSendPriceHint() Option { + return func(th *tapdHarnessParams) { + th.sendPriceHint = true + } +} + // setupTapdHarness creates a new tapd that connects to the given lnd node // and to the given universe server. func setupTapdHarness(t *testing.T, ht *harnessTest, @@ -420,6 +433,7 @@ func setupTapdHarness(t *testing.T, ht *harnessTest, ho.sqliteDatabaseFilePath = params.sqliteDatabaseFilePath ho.disableSyncCache = params.disableSyncCache ho.oracleServerAddress = params.oracleServerAddress + ho.sendPriceHint = params.sendPriceHint } tapdCfg := tapdConfig{ diff --git a/rfq/cli.go b/rfq/cli.go index c76c066a8..8559504c2 100644 --- a/rfq/cli.go +++ b/rfq/cli.go @@ -22,6 +22,10 @@ const ( type CliConfig struct { PriceOracleAddress string `long:"priceoracleaddress" description:"Price oracle gRPC server address (rfqrpc://:). To use the integrated mock, use the following value: use_mock_price_oracle_service_promise_to_not_use_on_mainnet"` + SendPriceHint bool `long:"sendpricehint" description:"Send a price hint from the local price oracle to the RFQ peer when requesting a quote. For privacy reasons, this should only be turned on for self-hosted or trusted price oracles."` + + PriceOracleSendPeerId bool `long:"priceoraclesendpeerid" description:"Send the peer ID (public key of the peer) to the price oracle when requesting a price rate. For privacy reasons, this should only be turned on for self-hosted or trusted price oracles."` + AcceptPriceDeviationPpm uint64 `long:"acceptpricedeviationppm" description:"The default price deviation in parts per million that is accepted by the RFQ negotiator"` SkipAcceptQuotePriceCheck bool `long:"skipacceptquotepricecheck" description:"Accept any price quote returned by RFQ peer, skipping price validation"` diff --git a/rfq/manager.go b/rfq/manager.go index 3faf5406f..de27084c8 100644 --- a/rfq/manager.go +++ b/rfq/manager.go @@ -127,6 +127,14 @@ type ManagerCfg struct { // wants us to produce NoOp HTLCs. NoOpHTLCs bool + // SendPriceHint is a flag that, if set, will send a price hint to the + // peer when requesting a quote. + SendPriceHint bool + + // SendPeerId is a flag that, if set, will send the peer ID (public + // key of the peer) to the price oracle when requesting a price rate. + SendPeerId bool + // ErrChan is the main error channel which will be used to report back // critical errors to the main server. ErrChan chan<- error @@ -277,16 +285,15 @@ func (m *Manager) startSubsystems(ctx context.Context) error { } // Initialise and start the quote negotiator. - m.negotiator, err = NewNegotiator( - // nolint: lll - NegotiatorCfg{ - PriceOracle: m.cfg.PriceOracle, - OutgoingMessages: m.outgoingMessages, - AcceptPriceDeviationPpm: m.cfg.AcceptPriceDeviationPpm, - SkipAcceptQuotePriceCheck: m.cfg.SkipAcceptQuotePriceCheck, - ErrChan: m.subsystemErrChan, - }, - ) + m.negotiator, err = NewNegotiator(NegotiatorCfg{ + PriceOracle: m.cfg.PriceOracle, + OutgoingMessages: m.outgoingMessages, + AcceptPriceDeviationPpm: m.cfg.AcceptPriceDeviationPpm, + SkipAcceptQuotePriceCheck: m.cfg.SkipAcceptQuotePriceCheck, + SendPriceHint: m.cfg.SendPriceHint, + SendPeerId: m.cfg.SendPeerId, + ErrChan: m.subsystemErrChan, + }) if err != nil { return fmt.Errorf("error initializing RFQ negotiator: %w", err) @@ -739,6 +746,14 @@ type BuyOrder struct { // TODO(ffranr): Currently, this field must be specified. In the future, // the negotiator should be able to determine the optimal peer. Peer fn.Option[route.Vertex] + + // PriceOracleMetadata is an optional text field that can be used to + // provide additional metadata about the buy order to the price + // oracle. This can include information about the wallet end user that + // initiated the transaction, or any authentication information that the + // price oracle can use to give out a more accurate (or discount) asset + // rate. The maximum length of this field is 32'768 bytes. + PriceOracleMetadata string } // UpsertAssetBuyOrder upserts an asset buy order for management. @@ -793,6 +808,14 @@ type SellOrder struct { // Peer is the peer that the buy order is intended for. This field is // optional. Peer fn.Option[route.Vertex] + + // PriceOracleMetadata is an optional text field that can be used to + // provide additional metadata about the sell order to the price + // oracle. This can include information about the wallet end user that + // initiated the transaction, or any authentication information that the + // price oracle can use to give out a more accurate (or discount) asset + // rate. The maximum length of this field is 32'768 bytes. + PriceOracleMetadata string } // UpsertAssetSellOrder upserts an asset sell order for management. @@ -1299,12 +1322,13 @@ func (m *Manager) publishSubscriberEvent(event fn.Event) { // out how many units of an asset are needed to evaluate to the provided amount // in milli satoshi. func EstimateAssetUnits(ctx context.Context, oracle PriceOracle, - specifier asset.Specifier, - amtMsat lnwire.MilliSatoshi) (uint64, error) { + specifier asset.Specifier, amtMsat lnwire.MilliSatoshi, + counterparty fn.Option[route.Vertex], metadata string, + intent PriceQueryIntent) (uint64, error) { - oracleRes, err := oracle.QueryBidPrice( + oracleRes, err := oracle.QueryBuyPrice( ctx, specifier, fn.None[uint64](), fn.Some(amtMsat), - fn.None[rfqmsg.AssetRate](), + fn.None[rfqmsg.AssetRate](), counterparty, metadata, intent, ) if err != nil { return 0, err diff --git a/rfq/marshal.go b/rfq/marshal.go index 6c8cd1030..40ae72297 100644 --- a/rfq/marshal.go +++ b/rfq/marshal.go @@ -45,34 +45,33 @@ func MarshalAcceptedSellQuote( Expiry: uint64(accept.AssetRate.Expiry.Unix()), AssetAmount: numAssetUnits.ScaleTo(0).ToUint64(), MinTransportableMsat: uint64(minTransportableMSat), + PriceOracleMetadata: accept.Request.PriceOracleMetadata, } } -// MarshalAcceptedBuyQuoteEvent marshals a peer accepted buy quote event to -// its rpc representation. -func MarshalAcceptedBuyQuoteEvent( - event *PeerAcceptedBuyQuoteEvent) (*rfqrpc.PeerAcceptedBuyQuote, - error) { - +// MarshalAcceptedBuyQuote marshals a peer accepted buy quote to its RPC +// representation. +func MarshalAcceptedBuyQuote(q rfqmsg.BuyAccept) *rfqrpc.PeerAcceptedBuyQuote { // We now calculate the minimum amount of asset units that can be // transported within a single HTLC for this asset at the given rate. // This corresponds to the 354 satoshi minimum non-dust HTLC value. minTransportableUnits := rfqmath.MinTransportableUnits( - rfqmath.DefaultOnChainHtlcMSat, event.AssetRate.Rate, + rfqmath.DefaultOnChainHtlcMSat, q.AssetRate.Rate, ).ScaleTo(0).ToUint64() return &rfqrpc.PeerAcceptedBuyQuote{ - Peer: event.Peer.String(), - Id: event.ID[:], - Scid: uint64(event.ShortChannelId()), - AssetMaxAmount: event.Request.AssetMaxAmt, + Peer: q.Peer.String(), + Id: q.ID[:], + Scid: uint64(q.ShortChannelId()), + AssetMaxAmount: q.Request.AssetMaxAmt, AskAssetRate: &rfqrpc.FixedPoint{ - Coefficient: event.AssetRate.Rate.Coefficient.String(), - Scale: uint32(event.AssetRate.Rate.Scale), + Coefficient: q.AssetRate.Rate.Coefficient.String(), + Scale: uint32(q.AssetRate.Rate.Scale), }, - Expiry: uint64(event.AssetRate.Expiry.Unix()), + Expiry: uint64(q.AssetRate.Expiry.Unix()), MinTransportableUnits: minTransportableUnits, - }, nil + PriceOracleMetadata: q.Request.PriceOracleMetadata, + } } // MarshalInvalidQuoteRespEvent marshals an invalid quote response event to @@ -112,13 +111,8 @@ func NewAddAssetBuyOrderResponse( switch e := event.(type) { case *PeerAcceptedBuyQuoteEvent: - acceptedQuote, err := MarshalAcceptedBuyQuoteEvent(e) - if err != nil { - return nil, err - } - resp.Response = &rfqrpc.AddAssetBuyOrderResponse_AcceptedQuote{ - AcceptedQuote: acceptedQuote, + AcceptedQuote: MarshalAcceptedBuyQuote(e.BuyAccept), } return resp, nil diff --git a/rfq/mock.go b/rfq/mock.go index bd2c58b1f..c5e83673f 100644 --- a/rfq/mock.go +++ b/rfq/mock.go @@ -10,6 +10,7 @@ import ( "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightninglabs/taproot-assets/rfqmsg" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/mock" ) @@ -50,16 +51,18 @@ func NewMockPriceOracleSatPerAsset(expiryDelay uint64, } } -// QueryAskPrice returns the ask price for the given asset amount. -func (m *MockPriceOracle) QueryAskPrice(ctx context.Context, +// QuerySellPrice returns the sell price for the given asset amount. +func (m *MockPriceOracle) QuerySellPrice(ctx context.Context, assetSpecifier asset.Specifier, assetMaxAmt fn.Option[uint64], paymentMaxAmt fn.Option[lnwire.MilliSatoshi], - assetRateHint fn.Option[rfqmsg.AssetRate]) (*OracleResponse, error) { + assetRateHint fn.Option[rfqmsg.AssetRate], + counterparty fn.Option[route.Vertex], metadata string, + intent PriceQueryIntent) (*OracleResponse, error) { // Return early with default value if no expected calls are predefined // for this method. - if !hasExpectedCall(m.ExpectedCalls, "QueryAskPrice") { + if !hasExpectedCall(m.ExpectedCalls, "QuerySellPrice") { // Calculate the rate expiry timestamp. lifetime := time.Duration(m.expiryDelay) * time.Second expiry := time.Now().Add(lifetime).UTC() @@ -71,24 +74,27 @@ func (m *MockPriceOracle) QueryAskPrice(ctx context.Context, }, nil } - // If an expected call exist, call normally. + // If an expected call exists, call normally. args := m.Called( ctx, assetSpecifier, assetMaxAmt, paymentMaxAmt, assetRateHint, + counterparty, metadata, intent, ) resp, _ := args.Get(0).(*OracleResponse) return resp, args.Error(1) } -// QueryBidPrice returns a bid price for the given asset amount. -func (m *MockPriceOracle) QueryBidPrice(ctx context.Context, +// QueryBuyPrice returns a buy price for the given asset amount. +func (m *MockPriceOracle) QueryBuyPrice(ctx context.Context, assetSpecifier asset.Specifier, assetMaxAmt fn.Option[uint64], paymentMaxAmt fn.Option[lnwire.MilliSatoshi], - assetRateHint fn.Option[rfqmsg.AssetRate]) (*OracleResponse, error) { + assetRateHint fn.Option[rfqmsg.AssetRate], + counterparty fn.Option[route.Vertex], metadata string, + intent PriceQueryIntent) (*OracleResponse, error) { // Return early with default value if no expected calls are predefined // for this method. - if !hasExpectedCall(m.ExpectedCalls, "QueryBidPrice") { + if !hasExpectedCall(m.ExpectedCalls, "QueryBuyPrice") { // Calculate the rate expiry timestamp. lifetime := time.Duration(m.expiryDelay) * time.Second expiry := time.Now().Add(lifetime).UTC() @@ -100,9 +106,10 @@ func (m *MockPriceOracle) QueryBidPrice(ctx context.Context, }, nil } - // If an expected call exist, call normally. + // If an expected call exists, call normally. args := m.Called( ctx, assetSpecifier, assetMaxAmt, paymentMaxAmt, assetRateHint, + counterparty, metadata, intent, ) resp, _ := args.Get(0).(*OracleResponse) return resp, args.Error(1) diff --git a/rfq/negotiator.go b/rfq/negotiator.go index dc64ed947..b7606129c 100644 --- a/rfq/negotiator.go +++ b/rfq/negotiator.go @@ -12,6 +12,7 @@ import ( "github.com/lightninglabs/taproot-assets/rfqmsg" "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" ) const ( @@ -48,6 +49,14 @@ type NegotiatorCfg struct { // useful for testing purposes. SkipAcceptQuotePriceCheck bool + // SendPriceHint is a flag that, if set, will send a price hint to the + // peer when requesting a quote. + SendPriceHint bool + + // SendPeerId is a flag that, if set, will send the peer ID (public + // key of the peer) to the price oracle when requesting a price rate. + SendPeerId bool + // ErrChan is a channel that is populated with errors by this subsystem. ErrChan chan<- error } @@ -102,51 +111,55 @@ func NewNegotiator(cfg NegotiatorCfg) (*Negotiator, error) { }, nil } -// queryBidFromPriceOracle queries the price oracle for a bid price. It returns -// an appropriate outgoing response message which should be sent to the peer. -func (n *Negotiator) queryBidFromPriceOracle(assetSpecifier asset.Specifier, +// queryBuyFromPriceOracle queries the price oracle for a buy price. +// It returns an appropriate outgoing response message which should be sent to +// the peer. +func (n *Negotiator) queryBuyFromPriceOracle(assetSpecifier asset.Specifier, assetMaxAmt fn.Option[uint64], paymentMaxAmt fn.Option[lnwire.MilliSatoshi], - assetRateHint fn.Option[rfqmsg.AssetRate]) (*rfqmsg.AssetRate, error) { + assetRateHint fn.Option[rfqmsg.AssetRate], + counterparty fn.Option[route.Vertex], metadata string, + intent PriceQueryIntent) (*rfqmsg.AssetRate, error) { - // TODO(ffranr): Optionally accept a peer's proposed ask price as an + // TODO(ffranr): Optionally accept a peer's proposed sell price as an // arg to this func and pass it to the price oracle. The price oracle - // service might be intelligent enough to use the peer's proposed ask - // price as a factor when computing the bid price. This argument must + // service might be intelligent enough to use the peer's proposed sell + // price as a factor when computing the buy price. This argument must // be optional because at some call sites we are initiating a request - // and do not have a peer's proposed ask price. + // and do not have a peer's proposed sell price. ctx, cancel := n.WithCtxQuitNoTimeout() defer cancel() - log.Debugf("Querying price oracle for bid price (asset_specifier=%s, "+ + log.Debugf("Querying price oracle for buy price (asset_specifier=%s, "+ "asset_max_amt=%s, payment_max_amt=%s, asset_rate_hint=%s)", assetSpecifier.String(), assetMaxAmt.String(), paymentMaxAmt.String(), assetRateHint.String()) - oracleResponse, err := n.cfg.PriceOracle.QueryBidPrice( + oracleResponse, err := n.cfg.PriceOracle.QueryBuyPrice( ctx, assetSpecifier, assetMaxAmt, paymentMaxAmt, assetRateHint, + counterparty, metadata, intent, ) if err != nil { return nil, fmt.Errorf("failed to query price oracle for "+ - "bid: %w", err) + "buy price: %w", err) } // Now we will check for an error in the response from the price oracle. // If present, we will convert it to a string and return it as an error. if oracleResponse.Err != nil { return nil, fmt.Errorf("failed to query price oracle for "+ - "bid price: %s", oracleResponse.Err) + "buy price: %s", oracleResponse.Err) } - // By this point, the price oracle did not return an error or a bid + // By this point, the price oracle did not return an error or a buy // price. We will therefore return an error. if oracleResponse.AssetRate.Rate.ToUint64() == 0 { return nil, fmt.Errorf("price oracle did not specify a " + - "bid price") + "buy price") } - // TODO(ffranr): Check that the bid price is reasonable. + // TODO(ffranr): Check that the buy price is reasonable. // TODO(ffranr): Ensure that the expiry time is valid and sufficient. return &oracleResponse.AssetRate, nil @@ -176,27 +189,37 @@ func (n *Negotiator) HandleOutgoingBuyOrder( return finalise(err) } - // We calculate a proposed bid price for our peer's + // We calculate a proposed buy price for our peer's // consideration. If a price oracle is not specified we will // skip this step. var assetRateHint fn.Option[rfqmsg.AssetRate] - if n.cfg.PriceOracle != nil && buyOrder.AssetSpecifier.IsSome() { - // Query the price oracle for a bid price. + if n.cfg.PriceOracle != nil && + buyOrder.AssetSpecifier.IsSome() && + n.cfg.SendPriceHint { + + var peerID fn.Option[route.Vertex] + if n.cfg.SendPeerId { + peerID = buyOrder.Peer + } + + // Query the price oracle for a buy price. // // TODO(ffranr): Pass the BuyOrder expiry to the price // oracle at this point. - assetRate, err := n.queryBidFromPriceOracle( + assetRate, err := n.queryBuyFromPriceOracle( buyOrder.AssetSpecifier, fn.Some(buyOrder.AssetMaxAmt), fn.None[lnwire.MilliSatoshi](), fn.None[rfqmsg.AssetRate](), + peerID, buyOrder.PriceOracleMetadata, + IntentRecvPaymentHint, ) if err != nil { // If we fail to query the price oracle for a - // bid price, we will log a warning and continue - // without a bid price. - log.Warnf("failed to query bid price from price "+ + // buy price, we will log a warning and continue + // without a buy price. + log.Warnf("failed to query buy price from price "+ "oracle for outgoing buy request: %v", err) } @@ -205,8 +228,8 @@ func (n *Negotiator) HandleOutgoingBuyOrder( // Construct a new buy request to send to the peer. request, err := rfqmsg.NewBuyRequest( - peer, buyOrder.AssetSpecifier, - buyOrder.AssetMaxAmt, assetRateHint, + peer, buyOrder.AssetSpecifier, buyOrder.AssetMaxAmt, + assetRateHint, buyOrder.PriceOracleMetadata, ) if err != nil { err := fmt.Errorf("unable to create buy request "+ @@ -230,47 +253,49 @@ func (n *Negotiator) HandleOutgoingBuyOrder( return request.ID, nil } -// queryAskFromPriceOracle queries the price oracle for an asking price. It +// querySellFromPriceOracle queries the price oracle for a sell price. It // returns an appropriate outgoing response message which should be sent to the // peer. -func (n *Negotiator) queryAskFromPriceOracle(assetSpecifier asset.Specifier, +func (n *Negotiator) querySellFromPriceOracle(assetSpecifier asset.Specifier, assetMaxAmt fn.Option[uint64], paymentMaxAmt fn.Option[lnwire.MilliSatoshi], - assetRateHint fn.Option[rfqmsg.AssetRate]) (*rfqmsg.AssetRate, error) { + assetRateHint fn.Option[rfqmsg.AssetRate], + counterparty fn.Option[route.Vertex], metadata string, + intent PriceQueryIntent) (*rfqmsg.AssetRate, error) { - // Query the price oracle for an asking price. + // Query the price oracle for a sell price. ctx, cancel := n.WithCtxQuitNoTimeout() defer cancel() - log.Debugf("Querying price oracle for ask price (asset_specifier=%s, "+ + log.Debugf("Querying price oracle for sell price (asset_specifier=%s, "+ "asset_max_amt=%s, payment_max_amt=%s, asset_rate_hint=%s)", assetSpecifier.String(), assetMaxAmt.String(), paymentMaxAmt.String(), assetRateHint.String()) - oracleResponse, err := n.cfg.PriceOracle.QueryAskPrice( - ctx, assetSpecifier, assetMaxAmt, paymentMaxAmt, - assetRateHint, + oracleResponse, err := n.cfg.PriceOracle.QuerySellPrice( + ctx, assetSpecifier, assetMaxAmt, paymentMaxAmt, assetRateHint, + counterparty, metadata, intent, ) if err != nil { return nil, fmt.Errorf("failed to query price oracle for "+ - "ask price: %w", err) + "sell price: %w", err) } // Now we will check for an error in the response from the price oracle. // If present, we will convert it to a string and return it as an error. if oracleResponse.Err != nil { return nil, fmt.Errorf("failed to query price oracle for "+ - "ask price: %s", oracleResponse.Err) + "sell price: %s", oracleResponse.Err) } - // By this point, the price oracle did not return an error or an asking + // By this point, the price oracle did not return an error or a sell // price. We will therefore return an error. if oracleResponse.AssetRate.Rate.Coefficient.ToUint64() == 0 { return nil, fmt.Errorf("price oracle did not specify an " + "asset to BTC rate") } - // TODO(ffranr): Check that the asking price is reasonable. + // TODO(ffranr): Check that the sell price is reasonable. // TODO(ffranr): Ensure that the expiry time is valid and sufficient. return &oracleResponse.AssetRate, nil @@ -335,12 +360,16 @@ func (n *Negotiator) HandleIncomingBuyRequest( go func() { defer n.Wg.Done() - // Query the price oracle for an asking price. - assetRate, err := n.queryAskFromPriceOracle( - request.AssetSpecifier, - fn.Some(request.AssetMaxAmt), - fn.None[lnwire.MilliSatoshi](), - request.AssetRateHint, + var peerID fn.Option[route.Vertex] + if n.cfg.SendPeerId { + peerID = fn.Some(request.Peer) + } + + // Query the price oracle for a sale price. + assetRate, err := n.querySellFromPriceOracle( + request.AssetSpecifier, fn.Some(request.AssetMaxAmt), + fn.None[lnwire.MilliSatoshi](), request.AssetRateHint, + peerID, request.PriceOracleMetadata, IntentRecvPayment, ) if err != nil { // Send a reject message to the peer. @@ -351,7 +380,7 @@ func (n *Negotiator) HandleIncomingBuyRequest( sendOutgoingMsg(msg) // Add an error to the error channel and return. - err = fmt.Errorf("failed to query ask price from "+ + err = fmt.Errorf("failed to query sell price from "+ "oracle: %w", err) n.cfg.ErrChan <- err return @@ -430,12 +459,18 @@ func (n *Negotiator) HandleIncomingSellRequest( go func() { defer n.Wg.Done() - // Query the price oracle for a bid price. This is the price we + var peerID fn.Option[route.Vertex] + if n.cfg.SendPeerId { + peerID = fn.Some(request.Peer) + } + + // Query the price oracle for a buy price. This is the price we // are willing to pay for the asset that our peer is trying to // sell to us. - assetRate, err := n.queryBidFromPriceOracle( + assetRate, err := n.queryBuyFromPriceOracle( request.AssetSpecifier, fn.None[uint64](), fn.Some(request.PaymentMaxAmt), request.AssetRateHint, + peerID, request.PriceOracleMetadata, IntentPayInvoice, ) if err != nil { // Send a reject message to the peer. @@ -446,7 +481,7 @@ func (n *Negotiator) HandleIncomingSellRequest( sendOutgoingMsg(msg) // Add an error to the error channel and return. - err = fmt.Errorf("failed to query bid price from "+ + err = fmt.Errorf("failed to query buy price from "+ "oracle: %w", err) n.cfg.ErrChan <- err return @@ -484,20 +519,28 @@ func (n *Negotiator) HandleOutgoingSellOrder( return finalise(err) } - // We calculate a proposed ask price for our peer's + // We calculate a proposed sell price for our peer's // consideration. If a price oracle is not specified we will // skip this step. var assetRateHint fn.Option[rfqmsg.AssetRate] - if n.cfg.PriceOracle != nil && order.AssetSpecifier.IsSome() { - // Query the price oracle for an asking price. + if n.cfg.PriceOracle != nil && order.AssetSpecifier.IsSome() && + n.cfg.SendPriceHint { + + var peerID fn.Option[route.Vertex] + if n.cfg.SendPeerId { + peerID = order.Peer + } + + // Query the price oracle for a sell price. // // TODO(ffranr): Pass the SellOrder expiry to the // price oracle at this point. - assetRate, err := n.queryAskFromPriceOracle( + assetRate, err := n.querySellFromPriceOracle( order.AssetSpecifier, fn.None[uint64](), fn.Some(order.PaymentMaxAmt), - fn.None[rfqmsg.AssetRate](), + fn.None[rfqmsg.AssetRate](), peerID, + order.PriceOracleMetadata, IntentPayInvoiceHint, ) if err != nil { err := fmt.Errorf("negotiator failed to handle price "+ @@ -510,6 +553,7 @@ func (n *Negotiator) HandleOutgoingSellOrder( request, err := rfqmsg.NewSellRequest( peer, order.AssetSpecifier, order.PaymentMaxAmt, assetRateHint, + order.PriceOracleMetadata, ) if err != nil { @@ -596,22 +640,29 @@ func (n *Negotiator) HandleIncomingBuyAccept(msg rfqmsg.BuyAccept, go func() { defer n.Wg.Done() - // The buy accept message includes an ask price, which + var peerID fn.Option[route.Vertex] + if n.cfg.SendPeerId { + peerID = fn.Some(msg.Peer) + } + + // The buy accept message includes an sell price, which // represents the amount the peer is willing to accept for the // asset we are purchasing. // - // To validate this ask, we will query our price oracle for a - // bid price and compare it with the peer's asking price. If the + // To validate this sell, we will query our price oracle for a + // buy price and compare it with the peer's sell price. If the // two prices fall within an acceptable tolerance, we will // approve the quote. // // When querying the price oracle, we will provide the peer's - // ask price as a hint. The oracle may factor this into its - // calculations to generate a more relevant bid price. - assetRate, err := n.queryBidFromPriceOracle( + // sell price as a hint. The oracle may factor this into its + // calculations to generate a more relevant buy price. + assetRate, err := n.queryBuyFromPriceOracle( msg.Request.AssetSpecifier, fn.Some(msg.Request.AssetMaxAmt), fn.None[lnwire.MilliSatoshi](), fn.Some(msg.AssetRate), + peerID, msg.Request.PriceOracleMetadata, + IntentRecvPaymentQualify, ) if err != nil { // The price oracle returned an error. We will return @@ -753,22 +804,29 @@ func (n *Negotiator) HandleIncomingSellAccept(msg rfqmsg.SellAccept, // Query the price oracle asynchronously using a separate goroutine. n.ContextGuard.Goroutine(func() error { - // The sell accept message includes a bid price, which + var peerID fn.Option[route.Vertex] + if n.cfg.SendPeerId { + peerID = fn.Some(msg.Peer) + } + + // The sell accept message includes a buy price, which // represents the amount the peer is willing to pay for the // asset we are selling. // - // To validate this bid, we will query our price oracle for an - // ask price and compare it with the peer's bid. If the two + // To validate this buy, we will query our price oracle for an + // sell price and compare it with the peer's buy. If the two // prices fall within an acceptable tolerance, we will accept // the quote. // // When querying the price oracle, we will provide the peer's - // bid as a hint. The oracle may incorporate this bid into its - // calculations to determine a more accurate ask price. - assetRate, err := n.queryAskFromPriceOracle( + // buy as a hint. The oracle may incorporate this buy into its + // calculations to determine a more accurate sell price. + assetRate, err := n.querySellFromPriceOracle( msg.Request.AssetSpecifier, fn.None[uint64](), fn.Some(msg.Request.PaymentMaxAmt), - fn.Some(msg.AssetRate), + fn.Some(msg.AssetRate), peerID, + msg.Request.PriceOracleMetadata, + IntentPayInvoiceQualify, ) if err != nil { // The price oracle returned an error. @@ -861,7 +919,7 @@ func (n *Negotiator) HandleIncomingSellAccept(msg rfqmsg.SellAccept, // for sale. // // A sell offer is passive (unlike a buy order), meaning that it does not -// actively lead to a bid request from a peer. Instead, it is used by the node +// actively lead to a buy request from a peer. Instead, it is used by the node // to selectively accept or reject incoming quote requests early before price // considerations. type SellOffer struct { diff --git a/rfq/negotiator_test.go b/rfq/negotiator_test.go index 738be0b8f..0f89c6548 100644 --- a/rfq/negotiator_test.go +++ b/rfq/negotiator_test.go @@ -43,10 +43,11 @@ func assertIncomingSellAcceptTestCase( mockPriceOracle := &MockPriceOracle{} // Register an expected call and response for price oracle method - // QueryAskPrice. + // QuerySellPrice. mockPriceOracle.On( - "QueryAskPrice", mock.Anything, mock.Anything, - mock.Anything, mock.Anything, mock.Anything, + "QuerySellPrice", mock.Anything, mock.Anything, + mock.Anything, mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything, ).Return( &OracleResponse{ AssetRate: tc.priceOracleAskPrice, @@ -228,10 +229,11 @@ func assertIncomingBuyAcceptTestCase( mockPriceOracle := &MockPriceOracle{} // Register an expected call and response for price oracle method - // QueryBidPrice. + // QueryBuyPrice. mockPriceOracle.On( - "QueryBidPrice", mock.Anything, mock.Anything, - mock.Anything, mock.Anything, mock.Anything, + "QueryBuyPrice", mock.Anything, mock.Anything, + mock.Anything, mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything, ).Return( &OracleResponse{ AssetRate: tc.priceOracleBidPrice, diff --git a/rfq/oracle.go b/rfq/oracle.go index 6fe85292b..e7ee28694 100644 --- a/rfq/oracle.go +++ b/rfq/oracle.go @@ -14,11 +14,74 @@ import ( "github.com/lightninglabs/taproot-assets/rpcutils" oraclerpc "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" ) +// PriceQueryIntent is an enum that represents the intent of a price rate +// query. It is used to indicate the purpose of the price rate request, such as +// whether the user is requesting a hint for paying an invoice, or if they are +// qualifying a rate for an invoice payment. This information is used by the +// price oracle service to provide the appropriate asset rate for the requested +// intent. +type PriceQueryIntent uint8 + +const ( + // IntentUnspecified is used to indicate that the intent of the price + // rate query is not specified. This is the fallback default value and + // should not be used in production code. It is primarily used for + // backward compatibility with older versions of the protocol that did + // not include intent information. + IntentUnspecified PriceQueryIntent = 0 + + // IntentPayInvoiceHint is used to indicate that the user is requesting + // a price rate hint for paying an invoice. This is typically used by + // the payer of an invoice to provide a suggestion of the expected asset + // rate to the RFQ peer (edge node) that will determine the actual rate + // for the payment. + IntentPayInvoiceHint PriceQueryIntent = 1 + + // IntentPayInvoice is used to indicate that a peer wants to pay an + // invoice with assets. This is typically used by the edge node that + // facilitates the swap from assets to BTC for the payer of an invoice. + // This intent is used to provide the actual asset rate for the payment, + // which may differ from the hint provided by the payer. + IntentPayInvoice PriceQueryIntent = 2 + + // IntentPayInvoiceQualify is used to indicate that the payer of an + // invoice has received an asset rate from their RFQ peer (edge node) + // and is qualifying the rate for the payment. This is typically used by + // the payer of an invoice to ensure that the asset rate provided by + // their peer (edge node) is acceptable before proceeding with the + // payment. + IntentPayInvoiceQualify PriceQueryIntent = 3 + + // IntentRecvPaymentHint is used to indicate that the user is requesting + // a price rate hint for receiving a payment through an invoice. This is + // typically used by the creator of an invoice to provide a suggestion + // of the expected asset rate to the RFQ peer (edge node) that will + // determine the actual rate used for creating an invoice. + IntentRecvPaymentHint PriceQueryIntent = 4 + + // IntentRecvPayment is used to indicate that a peer wants to create an + // invoice to receive a payment with assets. This is typically used by + // the edge node that facilitates the swap from BTC to assets for the + // receiver of a payment. This intent is used to provide the actual + // asset rate for the invoice creation, which may differ from the hint + // provided by the receiver. + IntentRecvPayment PriceQueryIntent = 5 + + // IntentRecvPaymentQualify is used to indicate that the creator of an + // invoice received an asset rate from their RFQ peer (edge node) and is + // qualifying the rate for the creation of the invoice. This is + // typically used by the creator of an invoice to ensure that the asset + // rate provided by their peer (edge node) is acceptable before + // proceeding with creating the invoice. + IntentRecvPaymentQualify PriceQueryIntent = 6 +) + // OracleError is a struct that holds an error returned by the price oracle // service. type OracleError struct { @@ -91,23 +154,25 @@ func ParsePriceOracleAddress(addrStr string) (*OracleAddr, error) { // PriceOracle is an interface that provides exchange rate information for // assets. type PriceOracle interface { - // QueryAskPrice returns the ask price for a given asset amount. - // The ask price is the amount the oracle suggests a peer should accept + // QuerySellPrice returns the sell price for a given asset amount. The + // sell price is the amount the oracle suggests a peer should accept // from another peer to provide the specified asset amount. - QueryAskPrice(ctx context.Context, assetSpecifier asset.Specifier, + QuerySellPrice(ctx context.Context, assetSpecifier asset.Specifier, assetMaxAmt fn.Option[uint64], paymentMaxAmt fn.Option[lnwire.MilliSatoshi], - assetRateHint fn.Option[rfqmsg.AssetRate]) ( - *OracleResponse, error) - - // QueryBidPrice returns the bid price for a given asset amount. - // The bid price is the amount the oracle suggests a peer should pay - // to another peer to receive the specified asset amount. - QueryBidPrice(ctx context.Context, assetSpecifier asset.Specifier, + assetRateHint fn.Option[rfqmsg.AssetRate], + counterparty fn.Option[route.Vertex], metadata string, + intent PriceQueryIntent) (*OracleResponse, error) + + // QueryBuyPrice returns the buy price for a given asset amount. The buy + // price is the amount the oracle suggests a peer should pay to another + // peer to receive the specified asset amount. + QueryBuyPrice(ctx context.Context, assetSpecifier asset.Specifier, assetMaxAmt fn.Option[uint64], paymentMaxAmt fn.Option[lnwire.MilliSatoshi], - assetRateHint fn.Option[rfqmsg.AssetRate]) ( - *OracleResponse, error) + assetRateHint fn.Option[rfqmsg.AssetRate], + counterparty fn.Option[route.Vertex], metadata string, + intent PriceQueryIntent) (*OracleResponse, error) } // RpcPriceOracle is a price oracle that uses an external RPC server to get @@ -189,12 +254,19 @@ func NewRpcPriceOracle(addrStr string, dialInsecure bool) (*RpcPriceOracle, }, nil } -// QueryAskPrice returns the ask price for the given asset amount. -func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context, +// QuerySellPrice returns the sell price for the given asset amount. +func (r *RpcPriceOracle) QuerySellPrice(ctx context.Context, assetSpecifier asset.Specifier, assetMaxAmt fn.Option[uint64], paymentMaxAmt fn.Option[lnwire.MilliSatoshi], - assetRateHint fn.Option[rfqmsg.AssetRate]) (*OracleResponse, - error) { + assetRateHint fn.Option[rfqmsg.AssetRate], + counterparty fn.Option[route.Vertex], metadata string, + intent PriceQueryIntent) (*OracleResponse, error) { + + if len(metadata) > rfqmsg.MaxOracleMetadataLength { + return nil, fmt.Errorf("metadata exceeds maximum length of %d "+ + "bytes: %d bytes", rfqmsg.MaxOracleMetadataLength, + len(metadata)) + } // The payment asset ID is BTC, so we leave it at all zeroes. var paymentAssetId = make([]byte, 32) @@ -215,6 +287,16 @@ func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context, paymentMaxAmt.UnwrapOr(lnwire.MilliSatoshi(0)), ) + rpcIntent, err := rpcMarshalIntent(intent) + if err != nil { + return nil, fmt.Errorf("failed to marshal intent: %w", err) + } + + var counterpartyBytes []byte + counterparty.WhenSome(func(c route.Vertex) { + counterpartyBytes = c[:] + }) + req := &oraclerpc.QueryAssetRatesRequest{ TransactionType: oraclerpc.TransactionType_SALE, SubjectAsset: rpcMarshalAssetSpecifier(assetSpecifier), @@ -226,6 +308,9 @@ func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context, }, PaymentAssetMaxAmount: paymentAssetMaxAmount, AssetRatesHint: rpcAssetRatesHint, + Intent: rpcIntent, + CounterpartyId: counterpartyBytes, + Metadata: metadata, } // Perform query. @@ -280,12 +365,19 @@ func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context, } } -// QueryBidPrice returns a bid price for the given asset amount. -func (r *RpcPriceOracle) QueryBidPrice(ctx context.Context, +// QueryBuyPrice returns a buy price for the given asset amount. +func (r *RpcPriceOracle) QueryBuyPrice(ctx context.Context, assetSpecifier asset.Specifier, assetMaxAmt fn.Option[uint64], paymentMaxAmt fn.Option[lnwire.MilliSatoshi], - assetRateHint fn.Option[rfqmsg.AssetRate]) (*OracleResponse, - error) { + assetRateHint fn.Option[rfqmsg.AssetRate], + counterparty fn.Option[route.Vertex], metadata string, + intent PriceQueryIntent) (*OracleResponse, error) { + + if len(metadata) > rfqmsg.MaxOracleMetadataLength { + return nil, fmt.Errorf("metadata exceeds maximum length of %d "+ + "bytes: %d bytes", rfqmsg.MaxOracleMetadataLength, + len(metadata)) + } // The payment asset ID is BTC, so we leave it at all zeroes. var paymentAssetId = make([]byte, 32) @@ -306,6 +398,16 @@ func (r *RpcPriceOracle) QueryBidPrice(ctx context.Context, return nil, err } + rpcIntent, err := rpcMarshalIntent(intent) + if err != nil { + return nil, fmt.Errorf("failed to marshal intent: %w", err) + } + + var counterpartyBytes []byte + counterparty.WhenSome(func(c route.Vertex) { + counterpartyBytes = c[:] + }) + req := &oraclerpc.QueryAssetRatesRequest{ TransactionType: oraclerpc.TransactionType_PURCHASE, SubjectAsset: rpcMarshalAssetSpecifier(assetSpecifier), @@ -317,6 +419,9 @@ func (r *RpcPriceOracle) QueryBidPrice(ctx context.Context, }, PaymentAssetMaxAmount: paymentAssetMaxAmount, AssetRatesHint: rpcAssetRatesHint, + Intent: rpcIntent, + CounterpartyId: counterpartyBytes, + Metadata: metadata, } // Perform query. @@ -397,3 +502,28 @@ func rpcMarshalAssetSpecifier( return &subjectSpecifier } + +// rpcMarshalIntent converts a PriceQueryIntent to the corresponding +// oraclerpc.Intent type. It returns an error if the intent is unknown. +func rpcMarshalIntent(intent PriceQueryIntent) (oraclerpc.Intent, + error) { + + switch intent { + case IntentUnspecified: + return oraclerpc.Intent_INTENT_UNSPECIFIED, nil + case IntentPayInvoiceHint: + return oraclerpc.Intent_INTENT_PAY_INVOICE_HINT, nil + case IntentPayInvoice: + return oraclerpc.Intent_INTENT_PAY_INVOICE, nil + case IntentPayInvoiceQualify: + return oraclerpc.Intent_INTENT_PAY_INVOICE_QUALIFY, nil + case IntentRecvPaymentHint: + return oraclerpc.Intent_INTENT_RECV_PAYMENT_HINT, nil + case IntentRecvPayment: + return oraclerpc.Intent_INTENT_RECV_PAYMENT, nil + case IntentRecvPaymentQualify: + return oraclerpc.Intent_INTENT_RECV_PAYMENT_QUALIFY, nil + default: + return 0, fmt.Errorf("unknown price query intent: %d", intent) + } +} diff --git a/rfq/oracle_test.go b/rfq/oracle_test.go index e012c4d4c..c120ffe72 100644 --- a/rfq/oracle_test.go +++ b/rfq/oracle_test.go @@ -16,6 +16,7 @@ import ( "github.com/lightninglabs/taproot-assets/rpcutils" "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -116,9 +117,9 @@ func startBackendRPC(t *testing.T, grpcServer *grpc.Server) string { return mockAddr } -// testCaseQueryAskPrice is a test case for the RPC price oracle client -// QueryAskPrice function. -type testCaseQueryAskPrice struct { +// testCaseQuerySalePrice is a test case for the RPC price oracle client +// QuerySellPrice function. +type testCaseQuerySalePrice struct { name string expectError bool @@ -129,8 +130,8 @@ type testCaseQueryAskPrice struct { suggestedAssetRate uint64 } -// runQueryAskPriceTest runs the RPC price oracle client QueryAskPrice test. -func runQueryAskPriceTest(t *testing.T, tc *testCaseQueryAskPrice) { +// runQuerySalePriceTest runs the RPC price oracle client QuerySellPrice test. +func runQuerySalePriceTest(t *testing.T, tc *testCaseQuerySalePrice) { // Start the mock RPC price oracle service. serverOpts := []grpc.ServerOption{ grpc.Creds(insecure.NewCredentials()), @@ -162,9 +163,10 @@ func runQueryAskPriceTest(t *testing.T, tc *testCaseQueryAskPrice) { ) require.NoError(t, err) - resp, err := client.QueryAskPrice( + resp, err := client.QuerySellPrice( ctx, assetSpecifier, fn.Some[uint64](assetMaxAmt), fn.None[lnwire.MilliSatoshi](), fn.Some(assetRateHint), + fn.None[route.Vertex](), "", IntentRecvPayment, ) // If we expect an error, ensure that it is returned. @@ -186,7 +188,7 @@ func runQueryAskPriceTest(t *testing.T, tc *testCaseQueryAskPrice) { require.True(t, resp.AssetRate.Expiry.After(time.Now())) } -// TestRpcPriceOracle tests the RPC price oracle client QueryAskPrice function. +// TestRpcPriceOracle tests the RPC price oracle client QuerySellPrice function. func TestRpcPriceOracleQueryAskPrice(t *testing.T) { // Create a random asset ID and asset group key. var assetId asset.ID @@ -194,7 +196,7 @@ func TestRpcPriceOracleQueryAskPrice(t *testing.T) { assetGroupKey := test.RandPubKey(t) - testCases := []*testCaseQueryAskPrice{ + testCases := []*testCaseQuerySalePrice{ { name: "asset ID only", assetId: &assetId, @@ -222,13 +224,13 @@ func TestRpcPriceOracleQueryAskPrice(t *testing.T) { } for _, tc := range testCases { - runQueryAskPriceTest(t, tc) + runQuerySalePriceTest(t, tc) } } -// testCaseQueryBidPrice is a test case for the RPC price oracle client -// QueryBidPrice function. -type testCaseQueryBidPrice struct { +// testCaseQueryPurchasePrice is a test case for the RPC price oracle client +// QueryBuyPrice function. +type testCaseQueryPurchasePrice struct { name string expectError bool @@ -237,8 +239,9 @@ type testCaseQueryBidPrice struct { assetGroupKey *btcec.PublicKey } -// runQueryBidPriceTest runs the RPC price oracle client QueryBidPrice test. -func runQueryBidPriceTest(t *testing.T, tc *testCaseQueryBidPrice) { +// runQueryPurchasePriceTest runs the RPC price oracle client QueryBuyPrice +// test. +func runQueryPurchasePriceTest(t *testing.T, tc *testCaseQueryPurchasePrice) { // Start the mock RPC price oracle service. serverOpts := []grpc.ServerOption{ grpc.Creds(insecure.NewCredentials()), @@ -263,10 +266,11 @@ func runQueryBidPriceTest(t *testing.T, tc *testCaseQueryBidPrice) { ) require.NoError(t, err) - resp, err := client.QueryBidPrice( + resp, err := client.QueryBuyPrice( ctx, assetSpecifier, fn.Some[uint64](assetMaxAmt), fn.None[lnwire.MilliSatoshi](), - fn.None[rfqmsg.AssetRate](), + fn.None[rfqmsg.AssetRate](), fn.None[route.Vertex](), "", + IntentPayInvoice, ) // If we expect an error, ensure that it is returned. @@ -288,15 +292,16 @@ func runQueryBidPriceTest(t *testing.T, tc *testCaseQueryBidPrice) { require.True(t, resp.AssetRate.Expiry.After(time.Now())) } -// TestRpcPriceOracle tests the RPC price oracle client QueryBidPrice function. -func TestRpcPriceOracleQueryBidPrice(t *testing.T) { +// TestRpcPriceOracleQueryPurchasePrice tests the RPC price oracle client +// QueryBuyPrice function. +func TestRpcPriceOracleQueryPurchasePrice(t *testing.T) { // Create a random asset ID and asset group key. var assetId asset.ID copy(assetId[:], test.RandBytes(32)) assetGroupKey := test.RandPubKey(t) - testCases := []*testCaseQueryBidPrice{ + testCases := []*testCaseQueryPurchasePrice{ { name: "asset ID only", assetId: &assetId, @@ -315,6 +320,6 @@ func TestRpcPriceOracleQueryBidPrice(t *testing.T) { } for _, tc := range testCases { - runQueryBidPriceTest(t, tc) + runQueryPurchasePriceTest(t, tc) } } diff --git a/rfqmsg/buy_request.go b/rfqmsg/buy_request.go index 9beccf1f2..a11dba6c3 100644 --- a/rfqmsg/buy_request.go +++ b/rfqmsg/buy_request.go @@ -59,12 +59,20 @@ type BuyRequest struct { // initiate the RFQ negotiation process and may differ from the final // agreed rate. AssetRateHint fn.Option[AssetRate] + + // PriceOracleMetadata is an optional text field that can be used to + // provide additional metadata about the buy request to the price + // oracle. This can include information about the wallet end user that + // initiated the transaction, or any authentication information that the + // price oracle can use to give out a more accurate (or discount) asset + // rate. The maximum length of this field is 32'768 bytes. + PriceOracleMetadata string } // NewBuyRequest creates a new asset buy quote request. func NewBuyRequest(peer route.Vertex, assetSpecifier asset.Specifier, - assetMaxAmt uint64, assetRateHint fn.Option[AssetRate]) (*BuyRequest, - error) { + assetMaxAmt uint64, assetRateHint fn.Option[AssetRate], + oracleMetadata string) (*BuyRequest, error) { id, err := NewID() if err != nil { @@ -72,13 +80,20 @@ func NewBuyRequest(peer route.Vertex, assetSpecifier asset.Specifier, "quote request id: %w", err) } + // Cap the user-defined string to avoid p2p message size limits. + if len(oracleMetadata) > MaxOracleMetadataLength { + return nil, fmt.Errorf("price oracle metadata exceeds maximum "+ + "length of %d bytes", MaxOracleMetadataLength) + } + return &BuyRequest{ - Peer: peer, - Version: latestBuyRequestVersion, - ID: id, - AssetSpecifier: assetSpecifier, - AssetMaxAmt: assetMaxAmt, - AssetRateHint: assetRateHint, + Peer: peer, + Version: latestBuyRequestVersion, + ID: id, + AssetSpecifier: assetSpecifier, + AssetMaxAmt: assetMaxAmt, + AssetRateHint: assetRateHint, + PriceOracleMetadata: oracleMetadata, }, nil } @@ -147,6 +162,10 @@ func NewBuyRequestFromWire(wireMsg WireMessage, AssetRateHint: assetRateHint, } + msgData.PriceOracleMetadata.ValOpt().WhenSome(func(metaBytes []byte) { + req.PriceOracleMetadata = string(metaBytes) + }) + // Perform basic sanity checks on the quote request. if err := req.Validate(); err != nil { return nil, fmt.Errorf("unable to validate buy request: %w", @@ -170,6 +189,12 @@ func (q *BuyRequest) Validate() error { q.Version) } + // Cap the user-defined string to avoid p2p message size limits. + if len(q.PriceOracleMetadata) > MaxOracleMetadataLength { + return fmt.Errorf("price oracle metadata exceeds maximum "+ + "length of %d bytes", MaxOracleMetadataLength) + } + // Ensure that the suggested asset rate has not expired. err = fn.MapOptionZ(q.AssetRateHint, func(rate AssetRate) error { if rate.Expiry.Before(time.Now()) { diff --git a/rfqmsg/messages.go b/rfqmsg/messages.go index 5d77d4913..fe5fe541a 100644 --- a/rfqmsg/messages.go +++ b/rfqmsg/messages.go @@ -181,8 +181,14 @@ func NewAssetRate(rate rfqmath.BigIntFixedPoint, expiry time.Time) AssetRate { } } -// MaxMessageType is the maximum supported message type value. -const MaxMessageType = lnwire.MessageType(math.MaxUint16) +const ( + // MaxMessageType is the maximum supported message type value. + MaxMessageType = lnwire.MessageType(math.MaxUint16) + + // MaxOracleMetadataLength is the maximum length of the oracle metadata + // field in a quote request message. + MaxOracleMetadataLength = 32_768 +) // TapMessageTypeBaseOffset is the taproot-assets specific message type // identifier base offset. All tap messages will have a type identifier that is diff --git a/rfqmsg/request.go b/rfqmsg/request.go index df1f1d81b..985fdb904 100644 --- a/rfqmsg/request.go +++ b/rfqmsg/request.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightninglabs/taproot-assets/asset" + lfn "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/tlv" ) @@ -53,6 +54,11 @@ type ( requestOutAssetRateHint = tlv.OptionalRecordT[ tlv.TlvType21, TlvFixedPoint, ] + + // requestOracleMetadata is a type alias for a record that represents + // the optional metadata that can be included in a quote request to + // provide additional context to the price oracle. + requestOracleMetadata = tlv.OptionalRecordT[tlv.TlvType27, []byte] ) // requestWireMsgData is a struct that represents the message data field for @@ -124,6 +130,14 @@ type requestWireMsgData struct { // transferred under the terms of the quote, applicable whether the // asset is BTC or any other. MinOutAsset tlv.OptionalRecordT[tlv.TlvType25, uint64] + + // PriceOracleMetadata is an optional text field that can be used to + // provide additional metadata about the buy request to the price + // oracle. This can include information about the wallet end user that + // initiated the transaction, or any authentication information that the + // price oracle can use to give out a more accurate (or discount) asset + // rate. The maximum length of this field is 32'768 bytes. + PriceOracleMetadata requestOracleMetadata } // newRequestWireMsgDataFromBuy creates a new requestWireMsgData from a buy @@ -178,18 +192,28 @@ func newRequestWireMsgDataFromBuy(q BuyRequest) (requestWireMsgData, error) { maxInAsset := tlv.NewPrimitiveRecord[tlv.TlvType16](q.AssetMaxAmt) + var oracleMetadata requestOracleMetadata + if len(q.PriceOracleMetadata) > 0 { + oracleMetadata = tlv.SomeRecordT[tlv.TlvType27]( + tlv.NewPrimitiveRecord[tlv.TlvType27]([]byte( + q.PriceOracleMetadata, + )), + ) + } + // Encode message data component as TLV bytes. return requestWireMsgData{ - Version: version, - ID: id, - TransferType: transferType, - Expiry: expiryTlv, - InAssetID: inAssetID, - InAssetGroupKey: inAssetGroupKey, - OutAssetID: outAssetID, - OutAssetGroupKey: outAssetGroupKey, - MaxInAsset: maxInAsset, - InAssetRateHint: inAssetRateHint, + Version: version, + ID: id, + TransferType: transferType, + Expiry: expiryTlv, + InAssetID: inAssetID, + InAssetGroupKey: inAssetGroupKey, + OutAssetID: outAssetID, + OutAssetGroupKey: outAssetGroupKey, + MaxInAsset: maxInAsset, + InAssetRateHint: inAssetRateHint, + PriceOracleMetadata: oracleMetadata, }, nil } @@ -248,17 +272,27 @@ func newRequestWireMsgDataFromSell(q SellRequest) (requestWireMsgData, error) { ) }) + var oracleMetadata requestOracleMetadata + if len(q.PriceOracleMetadata) > 0 { + oracleMetadata = tlv.SomeRecordT[tlv.TlvType27]( + tlv.NewPrimitiveRecord[tlv.TlvType27]([]byte( + q.PriceOracleMetadata, + )), + ) + } + // Encode message data component as TLV bytes. return requestWireMsgData{ - Version: version, - ID: id, - TransferType: transferType, - Expiry: expiryTlv, - InAssetID: inAssetID, - OutAssetID: outAssetID, - OutAssetGroupKey: outAssetGroupKey, - MaxInAsset: maxInAsset, - OutAssetRateHint: outAssetRateHint, + Version: version, + ID: id, + TransferType: transferType, + Expiry: expiryTlv, + InAssetID: inAssetID, + OutAssetID: outAssetID, + OutAssetGroupKey: outAssetGroupKey, + MaxInAsset: maxInAsset, + OutAssetRateHint: outAssetRateHint, + PriceOracleMetadata: oracleMetadata, }, nil } @@ -320,6 +354,14 @@ func (m *requestWireMsgData) Validate() error { "both be set to all zeros") } + if lfn.MapOptionZ(m.PriceOracleMetadata.ValOpt(), func(m []byte) bool { + return len(m) > MaxOracleMetadataLength + }) { + + return fmt.Errorf("price oracle metadata exceeds maximum "+ + "length of %d bytes", MaxOracleMetadataLength) + } + return nil } @@ -384,6 +426,11 @@ func (m *requestWireMsgData) Encode(w io.Writer) error { records = append(records, r.Record()) }, ) + m.PriceOracleMetadata.WhenSome( + func(r tlv.RecordT[tlv.TlvType27, []byte]) { + records = append(records, r.Record()) + }, + ) tlv.SortRecords(records) @@ -411,6 +458,8 @@ func (m *requestWireMsgData) Decode(r io.Reader) error { minInAsset := m.MinInAsset.Zero() minOutAsset := m.MinOutAsset.Zero() + oracleMetadata := m.PriceOracleMetadata.Zero() + // Create a tlv stream with all the fields. tlvStream, err := tlv.NewStream( m.Version.Record(), @@ -431,6 +480,8 @@ func (m *requestWireMsgData) Decode(r io.Reader) error { minInAsset.Record(), minOutAsset.Record(), + + oracleMetadata.Record(), ) if err != nil { return err @@ -470,6 +521,9 @@ func (m *requestWireMsgData) Decode(r io.Reader) error { if _, ok := tlvMap[minOutAsset.TlvType()]; ok { m.MinOutAsset = tlv.SomeRecordT(minOutAsset) } + if _, ok := tlvMap[oracleMetadata.TlvType()]; ok { + m.PriceOracleMetadata = tlv.SomeRecordT(oracleMetadata) + } return nil } diff --git a/rfqmsg/request_test.go b/rfqmsg/request_test.go index de92cd93e..017e3e583 100644 --- a/rfqmsg/request_test.go +++ b/rfqmsg/request_test.go @@ -33,6 +33,8 @@ type testCaseEncodeDecode struct { minInAsset *uint64 minOutAsset *uint64 + + oracleMetadata string } // Request generates a requestWireMsgData instance from the test case. @@ -107,19 +109,29 @@ func (tc testCaseEncodeDecode) Request() requestWireMsgData { ) } + var oracleMetadata requestOracleMetadata + if tc.oracleMetadata != "" { + oracleMetadata = tlv.SomeRecordT[tlv.TlvType27]( + tlv.NewPrimitiveRecord[tlv.TlvType27]([]byte( + tc.oracleMetadata, + )), + ) + } + return requestWireMsgData{ - Version: version, - ID: id, - Expiry: expiry, - InAssetID: inAssetID, - InAssetGroupKey: inAssetGroupKey, - OutAssetID: outAssetID, - OutAssetGroupKey: outAssetGroupKey, - MaxInAsset: maxInAsset, - InAssetRateHint: inAssetRateHint, - OutAssetRateHint: outAssetRateHint, - MinInAsset: minInAsset, - MinOutAsset: minOutAsset, + Version: version, + ID: id, + Expiry: expiry, + InAssetID: inAssetID, + InAssetGroupKey: inAssetGroupKey, + OutAssetID: outAssetID, + OutAssetGroupKey: outAssetGroupKey, + MaxInAsset: maxInAsset, + InAssetRateHint: inAssetRateHint, + OutAssetRateHint: outAssetRateHint, + MinInAsset: minInAsset, + MinOutAsset: minOutAsset, + PriceOracleMetadata: oracleMetadata, } } @@ -168,6 +180,7 @@ func TestRequestMsgDataEncodeDecode(t *testing.T) { outAssetRateHint: &outAssetRateHint, minInAsset: &minInAsset, minOutAsset: &minOutAsset, + oracleMetadata: "this could be a JSON string", }, { testName: "in asset ID, out asset ID zero, no asset " + diff --git a/rfqmsg/sell_request.go b/rfqmsg/sell_request.go index f9e3e427b..8debc4655 100644 --- a/rfqmsg/sell_request.go +++ b/rfqmsg/sell_request.go @@ -57,25 +57,40 @@ type SellRequest struct { // initiate the RFQ negotiation process and may differ from the final // agreed rate. AssetRateHint fn.Option[AssetRate] + + // PriceOracleMetadata is an optional text field that can be used to + // provide additional metadata about the sell request to the price + // oracle. This can include information about the wallet end user that + // initiated the transaction, or any authentication information that the + // price oracle can use to give out a more accurate (or discount) asset + // rate. The maximum length of this field is 32'768 bytes. + PriceOracleMetadata string } // NewSellRequest creates a new asset sell quote request. func NewSellRequest(peer route.Vertex, assetSpecifier asset.Specifier, - paymentMaxAmt lnwire.MilliSatoshi, - assetRateHint fn.Option[AssetRate]) (*SellRequest, error) { + paymentMaxAmt lnwire.MilliSatoshi, assetRateHint fn.Option[AssetRate], + oracleMetadata string) (*SellRequest, error) { id, err := NewID() if err != nil { return nil, fmt.Errorf("unable to generate random id: %w", err) } + // Cap the user-defined string to avoid p2p message size limits. + if len(oracleMetadata) > MaxOracleMetadataLength { + return nil, fmt.Errorf("price oracle metadata exceeds maximum "+ + "length of %d bytes", MaxOracleMetadataLength) + } + return &SellRequest{ - Peer: peer, - Version: latestSellRequestVersion, - ID: id, - AssetSpecifier: assetSpecifier, - PaymentMaxAmt: paymentMaxAmt, - AssetRateHint: assetRateHint, + Peer: peer, + Version: latestSellRequestVersion, + ID: id, + AssetSpecifier: assetSpecifier, + PaymentMaxAmt: paymentMaxAmt, + AssetRateHint: assetRateHint, + PriceOracleMetadata: oracleMetadata, }, nil } @@ -137,10 +152,16 @@ func NewSellRequestFromWire(wireMsg WireMessage, Version: msgData.Version.Val, ID: msgData.ID.Val, AssetSpecifier: assetSpecifier, - PaymentMaxAmt: lnwire.MilliSatoshi(msgData.MaxInAsset.Val), - AssetRateHint: assetRateHint, + PaymentMaxAmt: lnwire.MilliSatoshi( + msgData.MaxInAsset.Val, + ), + AssetRateHint: assetRateHint, } + msgData.PriceOracleMetadata.ValOpt().WhenSome(func(metaBytes []byte) { + req.PriceOracleMetadata = string(metaBytes) + }) + // Perform basic sanity checks on the quote request. if err := req.Validate(); err != nil { return nil, fmt.Errorf("unable to validate sell request: %w", @@ -164,6 +185,12 @@ func (q *SellRequest) Validate() error { "%d", q.Version) } + // Cap the user-defined string to avoid p2p message size limits. + if len(q.PriceOracleMetadata) > MaxOracleMetadataLength { + return fmt.Errorf("price oracle metadata exceeds maximum "+ + "length of %d bytes", MaxOracleMetadataLength) + } + return nil } diff --git a/rpcserver.go b/rpcserver.go index a9b488637..964c3b5d6 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -7721,11 +7721,18 @@ func unmarshalAssetBuyOrder( } expiry := time.Unix(int64(req.Expiry), 0).UTC() + if len(req.PriceOracleMetadata) > rfqmsg.MaxOracleMetadataLength { + return nil, fmt.Errorf("metadata exceeds maximum length of %d "+ + "bytes: %d bytes", rfqmsg.MaxOracleMetadataLength, + len(req.PriceOracleMetadata)) + } + return &rfq.BuyOrder{ - AssetSpecifier: assetSpecifier, - AssetMaxAmt: req.AssetMaxAmt, - Expiry: expiry, - Peer: fn.MaybeSome(peer), + AssetSpecifier: assetSpecifier, + AssetMaxAmt: req.AssetMaxAmt, + Expiry: expiry, + Peer: fn.MaybeSome(peer), + PriceOracleMetadata: req.PriceOracleMetadata, }, nil } @@ -7930,11 +7937,18 @@ func unmarshalAssetSellOrder( } expiry := time.Unix(int64(req.Expiry), 0).UTC() + if len(req.PriceOracleMetadata) > rfqmsg.MaxOracleMetadataLength { + return nil, fmt.Errorf("metadata exceeds maximum length of %d "+ + "bytes: %d bytes", rfqmsg.MaxOracleMetadataLength, + len(req.PriceOracleMetadata)) + } + return &rfq.SellOrder{ - AssetSpecifier: assetSpecifier, - PaymentMaxAmt: lnwire.MilliSatoshi(req.PaymentMaxAmt), - Expiry: expiry, - Peer: peer, + AssetSpecifier: assetSpecifier, + PaymentMaxAmt: lnwire.MilliSatoshi(req.PaymentMaxAmt), + Expiry: expiry, + Peer: peer, + PriceOracleMetadata: req.PriceOracleMetadata, }, nil } @@ -8123,50 +8137,6 @@ func (r *rpcServer) AddAssetBuyOffer(_ context.Context, return &rfqrpc.AddAssetBuyOfferResponse{}, nil } -// marshalPeerAcceptedBuyQuote marshals a peer accepted asset buy quote into -// the RPC form. This is a quote that was requested by our node and has been -// accepted by our peer. -func marshalPeerAcceptedBuyQuote( - quote rfqmsg.BuyAccept) *rfqrpc.PeerAcceptedBuyQuote { - - coefficient := quote.AssetRate.Rate.Coefficient.String() - rpcAskAssetRate := &rfqrpc.FixedPoint{ - Coefficient: coefficient, - Scale: uint32(quote.AssetRate.Rate.Scale), - } - - return &rfqrpc.PeerAcceptedBuyQuote{ - Peer: quote.Peer.String(), - Id: quote.ID[:], - Scid: uint64(quote.ShortChannelId()), - AssetMaxAmount: quote.Request.AssetMaxAmt, - AskAssetRate: rpcAskAssetRate, - Expiry: uint64(quote.AssetRate.Expiry.Unix()), - } -} - -// marshalPeerAcceptedSellQuote marshals peer accepted asset sell quote into the -// RPC form. This is a quote that was requested by our node and has been -// accepted by our peers. -func marshalPeerAcceptedSellQuote( - quote rfqmsg.SellAccept) *rfqrpc.PeerAcceptedSellQuote { - - rpcAssetRate := &rfqrpc.FixedPoint{ - Coefficient: quote.AssetRate.Rate.Coefficient.String(), - Scale: uint32(quote.AssetRate.Rate.Scale), - } - - // TODO(ffranr): Add SellRequest payment max amount to - // PeerAcceptedSellQuote. - return &rfqrpc.PeerAcceptedSellQuote{ - Peer: quote.Peer.String(), - Id: quote.ID[:], - Scid: uint64(quote.ShortChannelId()), - BidAssetRate: rpcAssetRate, - Expiry: uint64(quote.AssetRate.Expiry.Unix()), - } -} - // QueryPeerAcceptedQuotes is used to query for quotes that were requested by // our node and have been accepted our peers. func (r *rpcServer) QueryPeerAcceptedQuotes(_ context.Context, @@ -8179,11 +8149,11 @@ func (r *rpcServer) QueryPeerAcceptedQuotes(_ context.Context, peerAcceptedSellQuotes := r.cfg.RfqManager.PeerAcceptedSellQuotes() rpcBuyQuotes := fn.Map( - maps.Values(peerAcceptedBuyQuotes), marshalPeerAcceptedBuyQuote, + maps.Values(peerAcceptedBuyQuotes), rfq.MarshalAcceptedBuyQuote, ) rpcSellQuotes := fn.Map( maps.Values(peerAcceptedSellQuotes), - marshalPeerAcceptedSellQuote, + rfq.MarshalAcceptedSellQuote, ) return &rfqrpc.QueryPeerAcceptedQuotesResponse{ @@ -8198,10 +8168,7 @@ func marshallRfqEvent(eventInterface fn.Event) (*rfqrpc.RfqEvent, error) { switch event := eventInterface.(type) { case *rfq.PeerAcceptedBuyQuoteEvent: - acceptedQuote, err := rfq.MarshalAcceptedBuyQuoteEvent(event) - if err != nil { - return nil, err - } + acceptedQuote := rfq.MarshalAcceptedBuyQuote(event.BuyAccept) eventRpc := &rfqrpc.RfqEvent_PeerAcceptedBuyQuote{ PeerAcceptedBuyQuote: &rfqrpc.PeerAcceptedBuyQuoteEvent{ @@ -8614,7 +8581,7 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, // We'll store here all the quotes we acquired successfully. acquiredQuotes := r.acquirePaymentQuotes( ctx, &rpcSpecifier, paymentMaxAmt, expiry, chanMap, - req.AllowOverpay, payHash, + req.AllowOverpay, payHash, req.PriceOracleMetadata, ) // Send out the information about the acquired quotes on the @@ -8938,13 +8905,19 @@ func (r *rpcServer) AddInvoice(ctx context.Context, time.Duration(expirySeconds) * time.Second, ) + var peerID fn.Option[route.Vertex] + if r.cfg.PriceOracleSendPeerID { + peerID = fn.MaybeSome(peerPubKey) + } + // We now want to calculate the upper bound of the RFQ order, which // either is the asset amount specified by the user, or the converted // satoshi amount of the invoice, expressed in asset units, using the // local price oracle's conversion rate. maxUnits, err := calculateAssetMaxAmount( ctx, r.cfg.PriceOracle, specifier, req.AssetAmount, iReq, - r.cfg.RfqManager.GetPriceDeviationPpm(), + r.cfg.RfqManager.GetPriceDeviationPpm(), peerID, + req.PriceOracleMetadata, ) if err != nil { return nil, fmt.Errorf("error calculating asset max "+ @@ -8968,7 +8941,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context, quote, err := r.acquireBuyOrder( ctx, &rpcSpecifier, maxUnits, expiryTimestamp, - &peer, + &peer, req.PriceOracleMetadata, ) if err != nil { rpcsLog.Errorf("error while trying to acquire a buy "+ @@ -9035,7 +9008,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context, "quote in accepted quotes: %w", err) } - expensiveQuote = marshalPeerAcceptedBuyQuote(*buyQuote) + expensiveQuote = rfq.MarshalAcceptedBuyQuote(*buyQuote) } // Now that we have the accepted quote, we know the amount in (milli) @@ -9157,7 +9130,8 @@ func (r *rpcServer) AddInvoice(ctx context.Context, // a price oracle query will take place to calculate the max units of the quote. func calculateAssetMaxAmount(ctx context.Context, priceOracle rfq.PriceOracle, specifier asset.Specifier, requestAssetAmount uint64, - inv *lnrpc.Invoice, deviationPPM uint64) (uint64, error) { + inv *lnrpc.Invoice, deviationPPM uint64, + counterparty fn.Option[route.Vertex], metadata string) (uint64, error) { // Let's unmarshall the satoshi related fields to see if an amount was // set based on those. @@ -9186,7 +9160,8 @@ func calculateAssetMaxAmount(ctx context.Context, priceOracle rfq.PriceOracle, // will help us establish a quote with the correct amount of asset // units. maxUnits, err := rfq.EstimateAssetUnits( - ctx, priceOracle, specifier, amtMsat, + ctx, priceOracle, specifier, amtMsat, counterparty, metadata, + rfq.IntentPayInvoice, ) if err != nil { return 0, err @@ -9357,8 +9332,8 @@ func validateInvoiceAmount(acceptedQuote *rfqrpc.PeerAcceptedBuyQuote, // parameters and returns the quote if the negotiation was successful. func (r *rpcServer) acquireBuyOrder(ctx context.Context, rpcSpecifier *rfqrpc.AssetSpecifier, assetMaxAmt uint64, - expiryTimestamp time.Time, - peerPubKey *route.Vertex) (*rfqrpc.PeerAcceptedBuyQuote, error) { + expiryTimestamp time.Time, peerPubKey *route.Vertex, + metadata string) (*rfqrpc.PeerAcceptedBuyQuote, error) { var quote *rfqrpc.PeerAcceptedBuyQuote @@ -9370,6 +9345,7 @@ func (r *rpcServer) acquireBuyOrder(ctx context.Context, TimeoutSeconds: uint32( rfq.DefaultTimeout.Seconds(), ), + PriceOracleMetadata: metadata, }) if err != nil { return quote, fmt.Errorf("error adding buy order: %w", err) @@ -9409,8 +9385,8 @@ func (r *rpcServer) acquireBuyOrder(ctx context.Context, // is returned. func (r *rpcServer) acquirePaymentQuotes(ctx context.Context, rpcSpecifier *rfqrpc.AssetSpecifier, paymentMaxAmt lnwire.MilliSatoshi, - expiry time.Time, chanMap rfq.PeerChanMap, - overpay bool, payHash string) []*rfqrpc.PeerAcceptedSellQuote { + expiry time.Time, chanMap rfq.PeerChanMap, overpay bool, payHash string, + metadata string) []*rfqrpc.PeerAcceptedSellQuote { var acquiredQuotes []*rfqrpc.PeerAcceptedSellQuote @@ -9435,7 +9411,7 @@ func (r *rpcServer) acquirePaymentQuotes(ctx context.Context, quote, err := r.acquireSellQuote( rpcCtx, rpcSpecifier, paymentMaxAmt, - expiry, &peer, + expiry, &peer, metadata, ) if err != nil { fn.SendOrDone(collectorCtx, errorCollector, err) @@ -9484,8 +9460,8 @@ func (r *rpcServer) acquirePaymentQuotes(ctx context.Context, // parameters and returns the quote if the negotiation was successful. func (r *rpcServer) acquireSellQuote(ctx context.Context, rpcSpecifier *rfqrpc.AssetSpecifier, paymentMaxAmt lnwire.MilliSatoshi, - expiry time.Time, - peerPubKey *route.Vertex) (*rfqrpc.PeerAcceptedSellQuote, error) { + expiry time.Time, peerPubKey *route.Vertex, + metadata string) (*rfqrpc.PeerAcceptedSellQuote, error) { resp, err := r.AddAssetSellOrder( ctx, &rfqrpc.AddAssetSellOrderRequest{ @@ -9496,6 +9472,7 @@ func (r *rpcServer) acquireSellQuote(ctx context.Context, TimeoutSeconds: uint32( rfq.DefaultTimeout.Seconds(), ), + PriceOracleMetadata: metadata, }, ) if err != nil { @@ -9617,14 +9594,15 @@ func (r *rpcServer) getInboundPolicy(ctx context.Context, chanID uint64, // assetInvoiceAmt calculates the amount of asset units to pay for an invoice // which is expressed in sats. func (r *rpcServer) assetInvoiceAmt(ctx context.Context, - targetAsset asset.Specifier, + targetAsset asset.Specifier, metadata string, invoiceAmt lnwire.MilliSatoshi) (uint64, error) { oracle := r.cfg.PriceOracle - oracleResp, err := oracle.QueryAskPrice( + oracleResp, err := oracle.QuerySellPrice( ctx, targetAsset, fn.None[uint64](), fn.Some(invoiceAmt), - fn.None[rfqmsg.AssetRate](), + fn.None[rfqmsg.AssetRate](), fn.None[route.Vertex](), metadata, + rfq.IntentPayInvoice, ) if err != nil { return 0, fmt.Errorf("error querying ask price: %w", err) @@ -9785,7 +9763,9 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context, targetAsset := asset.NewSpecifierOptionalGroupKey( assetGroup.ID(), assetGroup.GroupKey, ) - invoiceAmt, err := r.assetInvoiceAmt(ctx, targetAsset, numMsat) + invoiceAmt, err := r.assetInvoiceAmt( + ctx, targetAsset, payReq.PriceOracleMetadata, numMsat, + ) if err != nil { return nil, fmt.Errorf("error deriving asset amount: %w", err) } diff --git a/sample-tapd.conf b/sample-tapd.conf index 08fa4c636..c83003abe 100644 --- a/sample-tapd.conf +++ b/sample-tapd.conf @@ -431,6 +431,16 @@ ; use_mock_price_oracle_service_promise_to_not_use_on_mainnet ; experimental.rfq.priceoracleaddress= +; Send a price hint from the local price oracle to the RFQ peer when requesting +; a quote. For privacy reasons, this should only be turned on for self-hosted or +; trusted price oracles. +; experimental.rfq.sendpricehint=false + +; Send the peer ID (public key of the peer) to the price oracle when requesting +; a price rate. For privacy reasons, this should only be turned on for +; self-hosted or trusted price oracles. +; experimental.rfq.priceoraclesendpeerid=false + ; The default price deviation inparts per million that is accepted by ; the RFQ negotiator. ; Example: 50,000 ppm => price deviation is set to 5% . diff --git a/tapcfg/server.go b/tapcfg/server.go index f11a39ac8..d715315de 100644 --- a/tapcfg/server.go +++ b/tapcfg/server.go @@ -456,23 +456,21 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, } // Construct the RFQ manager. - rfqManager, err := rfq.NewManager( - rfq.ManagerCfg{ - PeerMessenger: msgTransportClient, - HtlcInterceptor: lndRouterClient, - HtlcSubscriber: lndRouterClient, - PriceOracle: priceOracle, - ChannelLister: lndServices.Client, - GroupLookup: tapdbAddrBook, - AliasManager: lndRouterClient, - // nolint: lll - AcceptPriceDeviationPpm: rfqCfg.AcceptPriceDeviationPpm, - // nolint: lll - SkipAcceptQuotePriceCheck: rfqCfg.SkipAcceptQuotePriceCheck, - NoOpHTLCs: cfg.Channel.NoopHTLCs, - ErrChan: mainErrChan, - }, - ) + rfqManager, err := rfq.NewManager(rfq.ManagerCfg{ + PeerMessenger: msgTransportClient, + HtlcInterceptor: lndRouterClient, + HtlcSubscriber: lndRouterClient, + PriceOracle: priceOracle, + ChannelLister: lndServices.Client, + GroupLookup: tapdbAddrBook, + AliasManager: lndRouterClient, + AcceptPriceDeviationPpm: rfqCfg.AcceptPriceDeviationPpm, + SkipAcceptQuotePriceCheck: rfqCfg.SkipAcceptQuotePriceCheck, + SendPriceHint: rfqCfg.SendPriceHint, + SendPeerId: rfqCfg.PriceOracleSendPeerId, + NoOpHTLCs: cfg.Channel.NoopHTLCs, + ErrChan: mainErrChan, + }) if err != nil { return nil, err } @@ -626,6 +624,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, }, ) + // nolint: lll return &tap.Config{ DebugLevel: cfg.DebugLevel, RuntimeID: runtimeID, @@ -652,7 +651,6 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, ProofUpdates: proofArchive, ErrChan: mainErrChan, }), - // nolint: lll AssetCustodian: tapgarden.NewCustodian(&tapgarden.CustodianConfig{ ChainParams: &tapChainParams, WalletAnchor: walletAnchor, @@ -690,6 +688,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, UniverseQueriesBurst: cfg.Universe.UniverseQueriesBurst, RfqManager: rfqManager, PriceOracle: priceOracle, + PriceOracleSendPeerID: cfg.Experimental.Rfq.PriceOracleSendPeerId, AuxLeafSigner: auxLeafSigner, AuxFundingController: auxFundingController, AuxChanCloser: auxChanCloser, diff --git a/taprpc/priceoraclerpc/price_oracle.pb.go b/taprpc/priceoraclerpc/price_oracle.pb.go index 6becebda0..c0c0c6026 100644 --- a/taprpc/priceoraclerpc/price_oracle.pb.go +++ b/taprpc/priceoraclerpc/price_oracle.pb.go @@ -69,6 +69,109 @@ func (TransactionType) EnumDescriptor() ([]byte, []int) { return file_priceoraclerpc_price_oracle_proto_rawDescGZIP(), []int{0} } +// Intent is an enum informing the price oracle about the intent of the price +// rate query. This is used to provide context for the asset rates being +// requested, allowing the price oracle to tailor the response based on the +// specific use case, such as paying an invoice or receiving a payment and the +// different stages involved in those. +type Intent int32 + +const ( + // INTENT_UNSPECIFIED is used to indicate that the intent of the price rate + // query is not specified. This is the fallback default value and should not + // be used in production code. It is primarily used for backward + // compatibility with older versions of the protocol that did not include + // intent information. + Intent_INTENT_UNSPECIFIED Intent = 0 + // INTENT_PAY_INVOICE_HINT is used to indicate that the user is requesting + // a price rate hint for paying an invoice. This is typically used by the + // payer of an invoice to provide a suggestion of the expected asset rate to + // the RFQ peer (edge node) that will determine the actual rate for the + // payment. + Intent_INTENT_PAY_INVOICE_HINT Intent = 1 + // INTENT_PAY_INVOICE is used to indicate that a peer wants to pay an + // invoice with assets. This is typically used by the edge node that + // facilitates the swap from assets to BTC for the payer of an invoice. This + // intent is used to provide the actual asset rate for the payment, which + // may differ from the hint provided by the payer. + Intent_INTENT_PAY_INVOICE Intent = 2 + // INTENT_PAY_INVOICE_QUALIFY is used to indicate that the payer of an + // invoice has received an asset rate from their RFQ peer (edge node) and is + // qualifying the rate for the payment. This is typically used by the payer + // of an invoice to ensure that the asset rate provided by their peer (edge + // node) is acceptable before proceeding with the payment. + Intent_INTENT_PAY_INVOICE_QUALIFY Intent = 3 + // INTENT_RECV_PAYMENT_HINT is used to indicate that the user is requesting + // a price rate hint for receiving a payment through an invoice. This is + // typically used by the creator of an invoice to provide a suggestion of + // the expected asset rate to the RFQ peer (edge node) that will determine + // the actual rate used for creating an invoice. + Intent_INTENT_RECV_PAYMENT_HINT Intent = 4 + // INTENT_RECV_PAYMENT is used to indicate that a peer wants to create an + // invoice to receive a payment with assets. This is typically used by the + // edge node that facilitates the swap from BTC to assets for the receiver + // of a payment. This intent is used to provide the actual asset rate for + // the invoice creation, which may differ from the hint provided by the + // receiver. + Intent_INTENT_RECV_PAYMENT Intent = 5 + // INTENT_RECV_PAYMENT_QUALIFY is used to indicate that the creator of an + // invoice received an asset rate from their RFQ peer (edge node) and is + // qualifying the rate for the creation of the invoice. This is typically + // used by the creator of an invoice to ensure that the asset rate provided + // by their peer (edge node) is acceptable before proceeding with creating + // the invoice. + Intent_INTENT_RECV_PAYMENT_QUALIFY Intent = 6 +) + +// Enum value maps for Intent. +var ( + Intent_name = map[int32]string{ + 0: "INTENT_UNSPECIFIED", + 1: "INTENT_PAY_INVOICE_HINT", + 2: "INTENT_PAY_INVOICE", + 3: "INTENT_PAY_INVOICE_QUALIFY", + 4: "INTENT_RECV_PAYMENT_HINT", + 5: "INTENT_RECV_PAYMENT", + 6: "INTENT_RECV_PAYMENT_QUALIFY", + } + Intent_value = map[string]int32{ + "INTENT_UNSPECIFIED": 0, + "INTENT_PAY_INVOICE_HINT": 1, + "INTENT_PAY_INVOICE": 2, + "INTENT_PAY_INVOICE_QUALIFY": 3, + "INTENT_RECV_PAYMENT_HINT": 4, + "INTENT_RECV_PAYMENT": 5, + "INTENT_RECV_PAYMENT_QUALIFY": 6, + } +) + +func (x Intent) Enum() *Intent { + p := new(Intent) + *p = x + return p +} + +func (x Intent) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Intent) Descriptor() protoreflect.EnumDescriptor { + return file_priceoraclerpc_price_oracle_proto_enumTypes[1].Descriptor() +} + +func (Intent) Type() protoreflect.EnumType { + return &file_priceoraclerpc_price_oracle_proto_enumTypes[1] +} + +func (x Intent) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Intent.Descriptor instead. +func (Intent) EnumDescriptor() ([]byte, []int) { + return file_priceoraclerpc_price_oracle_proto_rawDescGZIP(), []int{1} +} + // FixedPoint is a scaled integer representation of a fractional number. // // This type consists of two integer fields: a coefficient and a scale. @@ -375,6 +478,29 @@ type QueryAssetRatesRequest struct { // asset_rates_hint is an optional suggestion of asset rates for the // transaction, intended to provide guidance on expected pricing. AssetRatesHint *AssetRates `protobuf:"bytes,6,opt,name=asset_rates_hint,json=assetRatesHint,proto3" json:"asset_rates_hint,omitempty"` + // intent informs the price oracle about the stage of the payment flow that + // lead to the price rate query. This is used to provide context for the + // asset rates being requested, allowing the price oracle to tailor the + // response based on the specific use case, such as paying an invoice or + // receiving a payment and the different stages involved in those. This + // field will only be set by tapd v0.7.0 and later. + Intent Intent `protobuf:"varint,7,opt,name=intent,proto3,enum=priceoraclerpc.Intent" json:"intent,omitempty"` + // counterparty_id is the 33-byte public key of the peer that is on the + // opposite side of the transaction. This field will only be set by tapd + // v0.7.0 and later and only if the user initiating the transaction (sending + // a payment or creating an invoice) opted in to sharing their peer ID with + // the price oracle. + CounterpartyId []byte `protobuf:"bytes,8,opt,name=counterparty_id,json=counterpartyId,proto3" json:"counterparty_id,omitempty"` + // metadata is an optional text field that can be used to provide + // additional metadata about the transaction to the price oracle. This can + // include information about the wallet end user that initiated the + // transaction, or any authentication information that the price oracle + // can use to give out a more accurate (or discount) asset rate. Though not + // verified or enforced by tapd, the suggested format for this field is a + // JSON string. This field is optional and can be left empty if no metadata + // is available. The maximum length of this field is 32'768 bytes. This + // field will only be set by tapd v0.7.0 and later. + Metadata string `protobuf:"bytes,9,opt,name=metadata,proto3" json:"metadata,omitempty"` } func (x *QueryAssetRatesRequest) Reset() { @@ -451,6 +577,27 @@ func (x *QueryAssetRatesRequest) GetAssetRatesHint() *AssetRates { return nil } +func (x *QueryAssetRatesRequest) GetIntent() Intent { + if x != nil { + return x.Intent + } + return Intent_INTENT_UNSPECIFIED +} + +func (x *QueryAssetRatesRequest) GetCounterpartyId() []byte { + if x != nil { + return x.CounterpartyId + } + return nil +} + +func (x *QueryAssetRatesRequest) GetMetadata() string { + if x != nil { + return x.Metadata + } + return "" +} + // QueryAssetRatesOkResponse is the successful response to a // QueryAssetRates call. type QueryAssetRatesOkResponse struct { @@ -676,7 +823,7 @@ var file_priceoraclerpc_price_oracle_proto_rawDesc = []byte{ 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x42, 0x04, 0x0a, 0x02, - 0x69, 0x64, 0x22, 0xa6, 0x03, 0x0a, 0x16, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, + 0x69, 0x64, 0x22, 0x9b, 0x04, 0x0a, 0x16, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4a, 0x0a, 0x10, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, @@ -702,42 +849,63 @@ var file_priceoraclerpc_price_oracle_proto_rawDesc = []byte{ 0x61, 0x74, 0x65, 0x73, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x0e, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, 0x6e, 0x74, 0x22, 0x58, 0x0a, 0x19, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x4f, 0x6b, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x52, 0x61, 0x74, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x1a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x45, 0x72, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x63, 0x6f, 0x64, - 0x65, 0x22, 0xa4, 0x01, 0x0a, 0x17, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, - 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x69, 0x63, - 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x4f, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x42, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x70, 0x72, 0x69, 0x63, - 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x45, 0x72, 0x72, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x08, - 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2a, 0x29, 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x50, - 0x55, 0x52, 0x43, 0x48, 0x41, 0x53, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x41, 0x4c, - 0x45, 0x10, 0x01, 0x32, 0x71, 0x0a, 0x0b, 0x50, 0x72, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x61, 0x63, - 0x6c, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x26, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, - 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, - 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, - 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, - 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x06, 0x69, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x61, 0x72, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x61, 0x72, + 0x74, 0x79, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x22, 0x58, 0x0a, 0x19, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, + 0x74, 0x65, 0x73, 0x4f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, + 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x0a, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x1a, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x45, 0x72, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0xa4, 0x01, 0x0a, 0x17, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, + 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x4f, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x02, 0x6f, 0x6b, 0x12, + 0x42, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, + 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x45, + 0x72, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2a, 0x29, 0x0a, + 0x0f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x55, 0x52, 0x43, 0x48, 0x41, 0x53, 0x45, 0x10, 0x00, 0x12, 0x08, + 0x0a, 0x04, 0x53, 0x41, 0x4c, 0x45, 0x10, 0x01, 0x2a, 0xcd, 0x01, 0x0a, 0x06, 0x49, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x49, + 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, + 0x45, 0x5f, 0x48, 0x49, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x54, 0x45, + 0x4e, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x10, 0x02, + 0x12, 0x1e, 0x0a, 0x1a, 0x49, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x5f, 0x49, + 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x51, 0x55, 0x41, 0x4c, 0x49, 0x46, 0x59, 0x10, 0x03, + 0x12, 0x1c, 0x0a, 0x18, 0x49, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x43, 0x56, 0x5f, + 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x48, 0x49, 0x4e, 0x54, 0x10, 0x04, 0x12, 0x17, + 0x0a, 0x13, 0x49, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x43, 0x56, 0x5f, 0x50, 0x41, + 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x05, 0x12, 0x1f, 0x0a, 0x1b, 0x49, 0x4e, 0x54, 0x45, 0x4e, + 0x54, 0x5f, 0x52, 0x45, 0x43, 0x56, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x51, + 0x55, 0x41, 0x4c, 0x49, 0x46, 0x59, 0x10, 0x06, 0x32, 0x71, 0x0a, 0x0b, 0x50, 0x72, 0x69, 0x63, + 0x65, 0x4f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x26, 0x2e, 0x70, 0x72, 0x69, + 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, + 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3f, 0x5a, 0x3d, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, + 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -752,35 +920,37 @@ func file_priceoraclerpc_price_oracle_proto_rawDescGZIP() []byte { return file_priceoraclerpc_price_oracle_proto_rawDescData } -var file_priceoraclerpc_price_oracle_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_priceoraclerpc_price_oracle_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_priceoraclerpc_price_oracle_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_priceoraclerpc_price_oracle_proto_goTypes = []any{ (TransactionType)(0), // 0: priceoraclerpc.TransactionType - (*FixedPoint)(nil), // 1: priceoraclerpc.FixedPoint - (*AssetRates)(nil), // 2: priceoraclerpc.AssetRates - (*AssetSpecifier)(nil), // 3: priceoraclerpc.AssetSpecifier - (*QueryAssetRatesRequest)(nil), // 4: priceoraclerpc.QueryAssetRatesRequest - (*QueryAssetRatesOkResponse)(nil), // 5: priceoraclerpc.QueryAssetRatesOkResponse - (*QueryAssetRatesErrResponse)(nil), // 6: priceoraclerpc.QueryAssetRatesErrResponse - (*QueryAssetRatesResponse)(nil), // 7: priceoraclerpc.QueryAssetRatesResponse + (Intent)(0), // 1: priceoraclerpc.Intent + (*FixedPoint)(nil), // 2: priceoraclerpc.FixedPoint + (*AssetRates)(nil), // 3: priceoraclerpc.AssetRates + (*AssetSpecifier)(nil), // 4: priceoraclerpc.AssetSpecifier + (*QueryAssetRatesRequest)(nil), // 5: priceoraclerpc.QueryAssetRatesRequest + (*QueryAssetRatesOkResponse)(nil), // 6: priceoraclerpc.QueryAssetRatesOkResponse + (*QueryAssetRatesErrResponse)(nil), // 7: priceoraclerpc.QueryAssetRatesErrResponse + (*QueryAssetRatesResponse)(nil), // 8: priceoraclerpc.QueryAssetRatesResponse } var file_priceoraclerpc_price_oracle_proto_depIdxs = []int32{ - 1, // 0: priceoraclerpc.AssetRates.subjectAssetRate:type_name -> priceoraclerpc.FixedPoint - 1, // 1: priceoraclerpc.AssetRates.paymentAssetRate:type_name -> priceoraclerpc.FixedPoint + 2, // 0: priceoraclerpc.AssetRates.subjectAssetRate:type_name -> priceoraclerpc.FixedPoint + 2, // 1: priceoraclerpc.AssetRates.paymentAssetRate:type_name -> priceoraclerpc.FixedPoint 0, // 2: priceoraclerpc.QueryAssetRatesRequest.transaction_type:type_name -> priceoraclerpc.TransactionType - 3, // 3: priceoraclerpc.QueryAssetRatesRequest.subject_asset:type_name -> priceoraclerpc.AssetSpecifier - 3, // 4: priceoraclerpc.QueryAssetRatesRequest.payment_asset:type_name -> priceoraclerpc.AssetSpecifier - 2, // 5: priceoraclerpc.QueryAssetRatesRequest.asset_rates_hint:type_name -> priceoraclerpc.AssetRates - 2, // 6: priceoraclerpc.QueryAssetRatesOkResponse.asset_rates:type_name -> priceoraclerpc.AssetRates - 5, // 7: priceoraclerpc.QueryAssetRatesResponse.ok:type_name -> priceoraclerpc.QueryAssetRatesOkResponse - 6, // 8: priceoraclerpc.QueryAssetRatesResponse.error:type_name -> priceoraclerpc.QueryAssetRatesErrResponse - 4, // 9: priceoraclerpc.PriceOracle.QueryAssetRates:input_type -> priceoraclerpc.QueryAssetRatesRequest - 7, // 10: priceoraclerpc.PriceOracle.QueryAssetRates:output_type -> priceoraclerpc.QueryAssetRatesResponse - 10, // [10:11] is the sub-list for method output_type - 9, // [9:10] is the sub-list for method input_type - 9, // [9:9] is the sub-list for extension type_name - 9, // [9:9] is the sub-list for extension extendee - 0, // [0:9] is the sub-list for field type_name + 4, // 3: priceoraclerpc.QueryAssetRatesRequest.subject_asset:type_name -> priceoraclerpc.AssetSpecifier + 4, // 4: priceoraclerpc.QueryAssetRatesRequest.payment_asset:type_name -> priceoraclerpc.AssetSpecifier + 3, // 5: priceoraclerpc.QueryAssetRatesRequest.asset_rates_hint:type_name -> priceoraclerpc.AssetRates + 1, // 6: priceoraclerpc.QueryAssetRatesRequest.intent:type_name -> priceoraclerpc.Intent + 3, // 7: priceoraclerpc.QueryAssetRatesOkResponse.asset_rates:type_name -> priceoraclerpc.AssetRates + 6, // 8: priceoraclerpc.QueryAssetRatesResponse.ok:type_name -> priceoraclerpc.QueryAssetRatesOkResponse + 7, // 9: priceoraclerpc.QueryAssetRatesResponse.error:type_name -> priceoraclerpc.QueryAssetRatesErrResponse + 5, // 10: priceoraclerpc.PriceOracle.QueryAssetRates:input_type -> priceoraclerpc.QueryAssetRatesRequest + 8, // 11: priceoraclerpc.PriceOracle.QueryAssetRates:output_type -> priceoraclerpc.QueryAssetRatesResponse + 11, // [11:12] is the sub-list for method output_type + 10, // [10:11] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name } func init() { file_priceoraclerpc_price_oracle_proto_init() } @@ -889,7 +1059,7 @@ func file_priceoraclerpc_price_oracle_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_priceoraclerpc_price_oracle_proto_rawDesc, - NumEnums: 1, + NumEnums: 2, NumMessages: 7, NumExtensions: 0, NumServices: 1, diff --git a/taprpc/priceoraclerpc/price_oracle.proto b/taprpc/priceoraclerpc/price_oracle.proto index fd0ab1f07..128ca4ae5 100644 --- a/taprpc/priceoraclerpc/price_oracle.proto +++ b/taprpc/priceoraclerpc/price_oracle.proto @@ -23,6 +23,64 @@ enum TransactionType { SALE = 1; } +// Intent is an enum informing the price oracle about the intent of the price +// rate query. This is used to provide context for the asset rates being +// requested, allowing the price oracle to tailor the response based on the +// specific use case, such as paying an invoice or receiving a payment and the +// different stages involved in those. +enum Intent { + // INTENT_UNSPECIFIED is used to indicate that the intent of the price rate + // query is not specified. This is the fallback default value and should not + // be used in production code. It is primarily used for backward + // compatibility with older versions of the protocol that did not include + // intent information. + INTENT_UNSPECIFIED = 0; + + // INTENT_PAY_INVOICE_HINT is used to indicate that the user is requesting + // a price rate hint for paying an invoice. This is typically used by the + // payer of an invoice to provide a suggestion of the expected asset rate to + // the RFQ peer (edge node) that will determine the actual rate for the + // payment. + INTENT_PAY_INVOICE_HINT = 1; + + // INTENT_PAY_INVOICE is used to indicate that a peer wants to pay an + // invoice with assets. This is typically used by the edge node that + // facilitates the swap from assets to BTC for the payer of an invoice. This + // intent is used to provide the actual asset rate for the payment, which + // may differ from the hint provided by the payer. + INTENT_PAY_INVOICE = 2; + + // INTENT_PAY_INVOICE_QUALIFY is used to indicate that the payer of an + // invoice has received an asset rate from their RFQ peer (edge node) and is + // qualifying the rate for the payment. This is typically used by the payer + // of an invoice to ensure that the asset rate provided by their peer (edge + // node) is acceptable before proceeding with the payment. + INTENT_PAY_INVOICE_QUALIFY = 3; + + // INTENT_RECV_PAYMENT_HINT is used to indicate that the user is requesting + // a price rate hint for receiving a payment through an invoice. This is + // typically used by the creator of an invoice to provide a suggestion of + // the expected asset rate to the RFQ peer (edge node) that will determine + // the actual rate used for creating an invoice. + INTENT_RECV_PAYMENT_HINT = 4; + + // INTENT_RECV_PAYMENT is used to indicate that a peer wants to create an + // invoice to receive a payment with assets. This is typically used by the + // edge node that facilitates the swap from BTC to assets for the receiver + // of a payment. This intent is used to provide the actual asset rate for + // the invoice creation, which may differ from the hint provided by the + // receiver. + INTENT_RECV_PAYMENT = 5; + + // INTENT_RECV_PAYMENT_QUALIFY is used to indicate that the creator of an + // invoice received an asset rate from their RFQ peer (edge node) and is + // qualifying the rate for the creation of the invoice. This is typically + // used by the creator of an invoice to ensure that the asset rate provided + // by their peer (edge node) is acceptable before proceeding with creating + // the invoice. + INTENT_RECV_PAYMENT_QUALIFY = 6; +} + // FixedPoint is a scaled integer representation of a fractional number. // // This type consists of two integer fields: a coefficient and a scale. @@ -129,6 +187,32 @@ message QueryAssetRatesRequest { // asset_rates_hint is an optional suggestion of asset rates for the // transaction, intended to provide guidance on expected pricing. AssetRates asset_rates_hint = 6; + + // intent informs the price oracle about the stage of the payment flow that + // lead to the price rate query. This is used to provide context for the + // asset rates being requested, allowing the price oracle to tailor the + // response based on the specific use case, such as paying an invoice or + // receiving a payment and the different stages involved in those. This + // field will only be set by tapd v0.7.0 and later. + Intent intent = 7; + + // counterparty_id is the 33-byte public key of the peer that is on the + // opposite side of the transaction. This field will only be set by tapd + // v0.7.0 and later and only if the user initiating the transaction (sending + // a payment or creating an invoice) opted in to sharing their peer ID with + // the price oracle. + bytes counterparty_id = 8; + + // metadata is an optional text field that can be used to provide + // additional metadata about the transaction to the price oracle. This can + // include information about the wallet end user that initiated the + // transaction, or any authentication information that the price oracle + // can use to give out a more accurate (or discount) asset rate. Though not + // verified or enforced by tapd, the suggested format for this field is a + // JSON string. This field is optional and can be left empty if no metadata + // is available. The maximum length of this field is 32'768 bytes. This + // field will only be set by tapd v0.7.0 and later. + string metadata = 9; } // QueryAssetRatesOkResponse is the successful response to a diff --git a/taprpc/priceoraclerpc/price_oracle.swagger.json b/taprpc/priceoraclerpc/price_oracle.swagger.json index d7987f6d5..04044cb66 100644 --- a/taprpc/priceoraclerpc/price_oracle.swagger.json +++ b/taprpc/priceoraclerpc/price_oracle.swagger.json @@ -160,6 +160,38 @@ "required": false, "type": "string", "format": "uint64" + }, + { + "name": "intent", + "description": "intent informs the price oracle about the stage of the payment flow that\nlead to the price rate query. This is used to provide context for the\nasset rates being requested, allowing the price oracle to tailor the\nresponse based on the specific use case, such as paying an invoice or\nreceiving a payment and the different stages involved in those. This\nfield will only be set by tapd v0.7.0 and later.\n\n - INTENT_UNSPECIFIED: INTENT_UNSPECIFIED is used to indicate that the intent of the price rate\nquery is not specified. This is the fallback default value and should not\nbe used in production code. It is primarily used for backward\ncompatibility with older versions of the protocol that did not include\nintent information.\n - INTENT_PAY_INVOICE_HINT: INTENT_PAY_INVOICE_HINT is used to indicate that the user is requesting\na price rate hint for paying an invoice. This is typically used by the\npayer of an invoice to provide a suggestion of the expected asset rate to\nthe RFQ peer (edge node) that will determine the actual rate for the\npayment.\n - INTENT_PAY_INVOICE: INTENT_PAY_INVOICE is used to indicate that a peer wants to pay an\ninvoice with assets. This is typically used by the edge node that\nfacilitates the swap from assets to BTC for the payer of an invoice. This\nintent is used to provide the actual asset rate for the payment, which\nmay differ from the hint provided by the payer.\n - INTENT_PAY_INVOICE_QUALIFY: INTENT_PAY_INVOICE_QUALIFY is used to indicate that the payer of an\ninvoice has received an asset rate from their RFQ peer (edge node) and is\nqualifying the rate for the payment. This is typically used by the payer\nof an invoice to ensure that the asset rate provided by their peer (edge\nnode) is acceptable before proceeding with the payment.\n - INTENT_RECV_PAYMENT_HINT: INTENT_RECV_PAYMENT_HINT is used to indicate that the user is requesting\na price rate hint for receiving a payment through an invoice. This is\ntypically used by the creator of an invoice to provide a suggestion of\nthe expected asset rate to the RFQ peer (edge node) that will determine\nthe actual rate used for creating an invoice.\n - INTENT_RECV_PAYMENT: INTENT_RECV_PAYMENT is used to indicate that a peer wants to create an\ninvoice to receive a payment with assets. This is typically used by the\nedge node that facilitates the swap from BTC to assets for the receiver\nof a payment. This intent is used to provide the actual asset rate for\nthe invoice creation, which may differ from the hint provided by the\nreceiver.\n - INTENT_RECV_PAYMENT_QUALIFY: INTENT_RECV_PAYMENT_QUALIFY is used to indicate that the creator of an\ninvoice received an asset rate from their RFQ peer (edge node) and is\nqualifying the rate for the creation of the invoice. This is typically\nused by the creator of an invoice to ensure that the asset rate provided\nby their peer (edge node) is acceptable before proceeding with creating\nthe invoice.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "INTENT_UNSPECIFIED", + "INTENT_PAY_INVOICE_HINT", + "INTENT_PAY_INVOICE", + "INTENT_PAY_INVOICE_QUALIFY", + "INTENT_RECV_PAYMENT_HINT", + "INTENT_RECV_PAYMENT", + "INTENT_RECV_PAYMENT_QUALIFY" + ], + "default": "INTENT_UNSPECIFIED" + }, + { + "name": "counterparty_id", + "description": "counterparty_id is the 33-byte public key of the peer that is on the\nopposite side of the transaction. This field will only be set by tapd\nv0.7.0 and later and only if the user initiating the transaction (sending\na payment or creating an invoice) opted in to sharing their peer ID with\nthe price oracle.", + "in": "query", + "required": false, + "type": "string", + "format": "byte" + }, + { + "name": "metadata", + "description": "metadata is an optional text field that can be used to provide\nadditional metadata about the transaction to the price oracle. This can\ninclude information about the wallet end user that initiated the\ntransaction, or any authentication information that the price oracle\ncan use to give out a more accurate (or discount) asset rate. Though not\nverified or enforced by tapd, the suggested format for this field is a\nJSON string. This field is optional and can be left empty if no metadata\nis available. The maximum length of this field is 32'768 bytes. This\nfield will only be set by tapd v0.7.0 and later.", + "in": "query", + "required": false, + "type": "string" } ], "tags": [ @@ -227,6 +259,20 @@ }, "description": "FixedPoint is a scaled integer representation of a fractional number.\n\nThis type consists of two integer fields: a coefficient and a scale.\nUsing this format enables precise and consistent representation of fractional\nnumbers while avoiding floating-point data types, which are prone to\nprecision errors.\n\nThe relationship between the fractional representation and its fixed-point\nrepresentation is expressed as:\n```\nV = F_c / (10^F_s)\n```\nwhere:\n\n* `V` is the fractional value.\n\n* `F_c` is the coefficient component of the fixed-point representation. It is\n the scaled-up fractional value represented as an integer.\n\n* `F_s` is the scale component. It is an integer specifying how\n many decimal places `F_c` should be divided by to obtain the fractional\n representation." }, + "priceoraclerpcIntent": { + "type": "string", + "enum": [ + "INTENT_UNSPECIFIED", + "INTENT_PAY_INVOICE_HINT", + "INTENT_PAY_INVOICE", + "INTENT_PAY_INVOICE_QUALIFY", + "INTENT_RECV_PAYMENT_HINT", + "INTENT_RECV_PAYMENT", + "INTENT_RECV_PAYMENT_QUALIFY" + ], + "default": "INTENT_UNSPECIFIED", + "description": "Intent is an enum informing the price oracle about the intent of the price\nrate query. This is used to provide context for the asset rates being\nrequested, allowing the price oracle to tailor the response based on the\nspecific use case, such as paying an invoice or receiving a payment and the\ndifferent stages involved in those.\n\n - INTENT_UNSPECIFIED: INTENT_UNSPECIFIED is used to indicate that the intent of the price rate\nquery is not specified. This is the fallback default value and should not\nbe used in production code. It is primarily used for backward\ncompatibility with older versions of the protocol that did not include\nintent information.\n - INTENT_PAY_INVOICE_HINT: INTENT_PAY_INVOICE_HINT is used to indicate that the user is requesting\na price rate hint for paying an invoice. This is typically used by the\npayer of an invoice to provide a suggestion of the expected asset rate to\nthe RFQ peer (edge node) that will determine the actual rate for the\npayment.\n - INTENT_PAY_INVOICE: INTENT_PAY_INVOICE is used to indicate that a peer wants to pay an\ninvoice with assets. This is typically used by the edge node that\nfacilitates the swap from assets to BTC for the payer of an invoice. This\nintent is used to provide the actual asset rate for the payment, which\nmay differ from the hint provided by the payer.\n - INTENT_PAY_INVOICE_QUALIFY: INTENT_PAY_INVOICE_QUALIFY is used to indicate that the payer of an\ninvoice has received an asset rate from their RFQ peer (edge node) and is\nqualifying the rate for the payment. This is typically used by the payer\nof an invoice to ensure that the asset rate provided by their peer (edge\nnode) is acceptable before proceeding with the payment.\n - INTENT_RECV_PAYMENT_HINT: INTENT_RECV_PAYMENT_HINT is used to indicate that the user is requesting\na price rate hint for receiving a payment through an invoice. This is\ntypically used by the creator of an invoice to provide a suggestion of\nthe expected asset rate to the RFQ peer (edge node) that will determine\nthe actual rate used for creating an invoice.\n - INTENT_RECV_PAYMENT: INTENT_RECV_PAYMENT is used to indicate that a peer wants to create an\ninvoice to receive a payment with assets. This is typically used by the\nedge node that facilitates the swap from BTC to assets for the receiver\nof a payment. This intent is used to provide the actual asset rate for\nthe invoice creation, which may differ from the hint provided by the\nreceiver.\n - INTENT_RECV_PAYMENT_QUALIFY: INTENT_RECV_PAYMENT_QUALIFY is used to indicate that the creator of an\ninvoice received an asset rate from their RFQ peer (edge node) and is\nqualifying the rate for the creation of the invoice. This is typically\nused by the creator of an invoice to ensure that the asset rate provided\nby their peer (edge node) is acceptable before proceeding with creating\nthe invoice." + }, "priceoraclerpcQueryAssetRatesErrResponse": { "type": "object", "properties": { diff --git a/taprpc/rfqrpc/rfq.pb.go b/taprpc/rfqrpc/rfq.pb.go index 985a2a0bd..4bc74eaed 100644 --- a/taprpc/rfqrpc/rfq.pb.go +++ b/taprpc/rfqrpc/rfq.pb.go @@ -295,6 +295,15 @@ type AddAssetBuyOrderRequest struct { // the RFQ negotiation to work. This flag shouldn't be set outside of test // scenarios. SkipAssetChannelCheck bool `protobuf:"varint,6,opt,name=skip_asset_channel_check,json=skipAssetChannelCheck,proto3" json:"skip_asset_channel_check,omitempty"` + // An optional text field that can be used to provide additional metadata + // about the buy order to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. Though not verified or enforced + // by tapd, the suggested format for this field is a JSON string. + // This field is optional and can be left empty if no metadata is available. + // The maximum length of this field is 32'768 bytes. + PriceOracleMetadata string `protobuf:"bytes,7,opt,name=price_oracle_metadata,json=priceOracleMetadata,proto3" json:"price_oracle_metadata,omitempty"` } func (x *AddAssetBuyOrderRequest) Reset() { @@ -371,6 +380,13 @@ func (x *AddAssetBuyOrderRequest) GetSkipAssetChannelCheck() bool { return false } +func (x *AddAssetBuyOrderRequest) GetPriceOracleMetadata() string { + if x != nil { + return x.PriceOracleMetadata + } + return "" +} + type AddAssetBuyOrderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -495,6 +511,15 @@ type AddAssetSellOrderRequest struct { // the RFQ negotiation to work. This flag shouldn't be set outside of test // scenarios. SkipAssetChannelCheck bool `protobuf:"varint,6,opt,name=skip_asset_channel_check,json=skipAssetChannelCheck,proto3" json:"skip_asset_channel_check,omitempty"` + // An optional text field that can be used to provide additional metadata + // about the sell order to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. Though not verified or enforced + // by tapd, the suggested format for this field is a JSON string. + // This field is optional and can be left empty if no metadata is available. + // The maximum length of this field is 32'768 bytes. + PriceOracleMetadata string `protobuf:"bytes,7,opt,name=price_oracle_metadata,json=priceOracleMetadata,proto3" json:"price_oracle_metadata,omitempty"` } func (x *AddAssetSellOrderRequest) Reset() { @@ -571,6 +596,13 @@ func (x *AddAssetSellOrderRequest) GetSkipAssetChannelCheck() bool { return false } +func (x *AddAssetSellOrderRequest) GetPriceOracleMetadata() string { + if x != nil { + return x.PriceOracleMetadata + } + return "" +} + type AddAssetSellOrderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -927,6 +959,12 @@ type PeerAcceptedBuyQuote struct { // asset unit equivalent of 354 satoshis, which is the minimum amount for an // HTLC to be above the dust limit. MinTransportableUnits uint64 `protobuf:"varint,7,opt,name=min_transportable_units,json=minTransportableUnits,proto3" json:"min_transportable_units,omitempty"` + // An optional user-provided text field used to provide additional metadata + // about the buy order to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. + PriceOracleMetadata string `protobuf:"bytes,8,opt,name=price_oracle_metadata,json=priceOracleMetadata,proto3" json:"price_oracle_metadata,omitempty"` } func (x *PeerAcceptedBuyQuote) Reset() { @@ -1010,6 +1048,13 @@ func (x *PeerAcceptedBuyQuote) GetMinTransportableUnits() uint64 { return 0 } +func (x *PeerAcceptedBuyQuote) GetPriceOracleMetadata() string { + if x != nil { + return x.PriceOracleMetadata + } + return "" +} + type PeerAcceptedSellQuote struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1035,6 +1080,12 @@ type PeerAcceptedSellQuote struct { // amount for a non-dust HTLC) plus the equivalent of one asset unit in // milli-satoshis. MinTransportableMsat uint64 `protobuf:"varint,7,opt,name=min_transportable_msat,json=minTransportableMsat,proto3" json:"min_transportable_msat,omitempty"` + // An optional user-provided text field used to provide additional metadata + // about the sell order to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. + PriceOracleMetadata string `protobuf:"bytes,8,opt,name=price_oracle_metadata,json=priceOracleMetadata,proto3" json:"price_oracle_metadata,omitempty"` } func (x *PeerAcceptedSellQuote) Reset() { @@ -1118,6 +1169,13 @@ func (x *PeerAcceptedSellQuote) GetMinTransportableMsat() uint64 { return 0 } +func (x *PeerAcceptedSellQuote) GetPriceOracleMetadata() string { + if x != nil { + return x.PriceOracleMetadata + } + return "" +} + // InvalidQuoteResponse is a message that is returned when a quote response is // invalid or insufficient. type InvalidQuoteResponse struct { @@ -1652,7 +1710,7 @@ var file_rfqrpc_rfq_proto_rawDesc = []byte{ 0x69, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x22, - 0x9a, 0x02, 0x0a, 0x17, 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, 0x4f, + 0xce, 0x02, 0x0a, 0x17, 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, @@ -1669,110 +1727,123 @@ var file_rfqrpc_rfq_proto_rawDesc = []byte{ 0x6e, 0x64, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x22, 0xfa, 0x01, 0x0a, - 0x18, 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0e, 0x61, 0x63, 0x63, - 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x48, - 0x00, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, - 0x12, 0x43, 0x0a, 0x0d, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, - 0x2e, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x46, 0x0a, 0x0e, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, - 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, - 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x51, - 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0d, - 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x42, 0x0a, 0x0a, - 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9f, 0x02, 0x0a, 0x18, 0x41, 0x64, - 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, - 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x70, - 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0e, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x70, - 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x78, 0x41, 0x6d, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, - 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, - 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x69, 0x6d, - 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x22, 0xfc, 0x01, 0x0a, 0x19, - 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0e, 0x61, 0x63, 0x63, - 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, - 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x51, 0x75, 0x6f, 0x74, - 0x65, 0x12, 0x43, 0x0a, 0x0d, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x71, 0x75, 0x6f, - 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, - 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x46, 0x0a, 0x0e, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, - 0x65, 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, - 0x0d, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x42, 0x0a, - 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x78, 0x0a, 0x18, 0x41, 0x64, - 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, - 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x70, - 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0e, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x70, - 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x75, - 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x55, - 0x6e, 0x69, 0x74, 0x73, 0x22, 0x1b, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x77, 0x0a, 0x17, 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, - 0x4f, 0x66, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0f, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0e, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1b, 0x0a, - 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x08, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x22, 0x1a, 0x0a, 0x18, 0x41, 0x64, - 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x0a, 0x1e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, - 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a, 0x14, 0x50, 0x65, 0x65, - 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, 0x51, 0x75, 0x6f, 0x74, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x70, 0x65, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x63, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0e, 0x61, 0x73, 0x73, 0x65, 0x74, 0x4d, 0x61, 0x78, 0x41, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x0e, 0x61, 0x73, 0x6b, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x66, - 0x71, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x78, 0x65, 0x64, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x0c, 0x61, 0x73, 0x6b, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x36, 0x0a, 0x17, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x6d, 0x69, 0x6e, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x70, 0x6f, 0x72, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x22, 0xfa, 0x01, - 0x0a, 0x15, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, - 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, - 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x63, 0x69, 0x64, 0x12, - 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x0e, 0x62, 0x69, 0x64, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, - 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x66, 0x71, - 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x78, 0x65, 0x64, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, - 0x62, 0x69, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x65, 0x78, - 0x70, 0x69, 0x72, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x6d, 0x69, 0x6e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, - 0x72, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x6b, 0x0a, 0x14, 0x49, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x32, 0x0a, 0x15, + 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x69, + 0x63, 0x65, 0x4f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x22, 0xfa, 0x01, 0x0a, 0x18, 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, + 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, 0x51, 0x75, + 0x6f, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x51, + 0x75, 0x6f, 0x74, 0x65, 0x12, 0x43, 0x0a, 0x0d, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, + 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x66, + 0x71, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x51, 0x75, 0x6f, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x69, 0x6e, 0x76, + 0x61, 0x6c, 0x69, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x46, 0x0a, 0x0e, 0x72, 0x65, 0x6a, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x48, 0x00, 0x52, 0x0d, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x51, 0x75, 0x6f, 0x74, + 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd3, 0x02, + 0x0a, 0x18, 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0f, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0e, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x78, + 0x41, 0x6d, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0c, 0x70, + 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x27, 0x0a, + 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, + 0x32, 0x0a, 0x15, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x5f, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, + 0x70, 0x72, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x22, 0xfc, 0x01, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x46, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x71, 0x75, + 0x6f, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, + 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, + 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x65, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x43, 0x0a, 0x0d, 0x69, 0x6e, 0x76, + 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, + 0x52, 0x0c, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x46, + 0x0a, 0x0e, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x78, 0x0a, 0x18, 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x65, + 0x6c, 0x6c, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, + 0x0a, 0x0f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, + 0x0e, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, + 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x22, 0x1b, 0x0a, 0x19, + 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x66, 0x66, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x77, 0x0a, 0x17, 0x41, 0x64, 0x64, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73, 0x70, + 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0e, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x6e, 0x69, + 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x69, + 0x74, 0x73, 0x22, 0x1a, 0x0a, 0x18, 0x41, 0x64, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, + 0x79, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, + 0x0a, 0x1e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x65, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0xb6, 0x02, 0x0a, 0x14, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, + 0x64, 0x42, 0x75, 0x79, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x73, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x63, 0x69, + 0x64, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x4d, 0x61, 0x78, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x0e, 0x61, + 0x73, 0x6b, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x78, + 0x65, 0x64, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x61, 0x73, 0x6b, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x52, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x36, 0x0a, + 0x17, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, + 0x6d, 0x69, 0x6e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x55, 0x6e, 0x69, 0x74, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x6f, + 0x72, 0x61, 0x63, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x61, 0x63, 0x6c, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xae, 0x02, 0x0a, 0x15, 0x50, 0x65, + 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, + 0x6f, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x63, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x63, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x38, + 0x0a, 0x0e, 0x62, 0x69, 0x64, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x69, 0x78, 0x65, 0x64, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x62, 0x69, 0x64, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x14, 0x6d, 0x69, 0x6e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, + 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x61, 0x63, + 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x6b, 0x0a, 0x14, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x6f, 0x74, diff --git a/taprpc/rfqrpc/rfq.proto b/taprpc/rfqrpc/rfq.proto index d37acf883..5e4d5db1f 100644 --- a/taprpc/rfqrpc/rfq.proto +++ b/taprpc/rfqrpc/rfq.proto @@ -144,6 +144,16 @@ message AddAssetBuyOrderRequest { // the RFQ negotiation to work. This flag shouldn't be set outside of test // scenarios. bool skip_asset_channel_check = 6; + + // An optional text field that can be used to provide additional metadata + // about the buy order to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. Though not verified or enforced + // by tapd, the suggested format for this field is a JSON string. + // This field is optional and can be left empty if no metadata is available. + // The maximum length of this field is 32'768 bytes. + string price_oracle_metadata = 7; } message AddAssetBuyOrderResponse { @@ -186,6 +196,16 @@ message AddAssetSellOrderRequest { // the RFQ negotiation to work. This flag shouldn't be set outside of test // scenarios. bool skip_asset_channel_check = 6; + + // An optional text field that can be used to provide additional metadata + // about the sell order to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. Though not verified or enforced + // by tapd, the suggested format for this field is a JSON string. + // This field is optional and can be left empty if no metadata is available. + // The maximum length of this field is 32'768 bytes. + string price_oracle_metadata = 7; } message AddAssetSellOrderResponse { @@ -258,6 +278,13 @@ message PeerAcceptedBuyQuote { // asset unit equivalent of 354 satoshis, which is the minimum amount for an // HTLC to be above the dust limit. uint64 min_transportable_units = 7; + + // An optional user-provided text field used to provide additional metadata + // about the buy order to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. + string price_oracle_metadata = 8; } message PeerAcceptedSellQuote { @@ -287,6 +314,13 @@ message PeerAcceptedSellQuote { // amount for a non-dust HTLC) plus the equivalent of one asset unit in // milli-satoshis. uint64 min_transportable_msat = 7; + + // An optional user-provided text field used to provide additional metadata + // about the sell order to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. + string price_oracle_metadata = 8; } // QuoteRespStatus is an enum that represents the status of a quote response. diff --git a/taprpc/rfqrpc/rfq.swagger.json b/taprpc/rfqrpc/rfq.swagger.json index 9270b51a5..890da0e67 100644 --- a/taprpc/rfqrpc/rfq.swagger.json +++ b/taprpc/rfqrpc/rfq.swagger.json @@ -485,6 +485,10 @@ "skip_asset_channel_check": { "type": "boolean", "description": "If set, the check if a channel with the given asset exists with the peer\nwill be skipped. An active channel with the peer is still required for\nthe RFQ negotiation to work. This flag shouldn't be set outside of test\nscenarios." + }, + "price_oracle_metadata": { + "type": "string", + "description": "An optional text field that can be used to provide additional metadata\nabout the buy order to the price oracle. This can include information\nabout the wallet end user that initiated the transaction, or any\nauthentication information that the price oracle can use to give out a\nmore accurate (or discount) asset rate. Though not verified or enforced\nby tapd, the suggested format for this field is a JSON string.\nThis field is optional and can be left empty if no metadata is available.\nThe maximum length of this field is 32'768 bytes." } } }, @@ -566,6 +570,10 @@ "skip_asset_channel_check": { "type": "boolean", "description": "If set, the check if a channel with the given asset exists with the peer\nwill be skipped. An active channel with the peer is still required for\nthe RFQ negotiation to work. This flag shouldn't be set outside of test\nscenarios." + }, + "price_oracle_metadata": { + "type": "string", + "description": "An optional text field that can be used to provide additional metadata\nabout the sell order to the price oracle. This can include information\nabout the wallet end user that initiated the transaction, or any\nauthentication information that the price oracle can use to give out a\nmore accurate (or discount) asset rate. Though not verified or enforced\nby tapd, the suggested format for this field is a JSON string.\nThis field is optional and can be left empty if no metadata is available.\nThe maximum length of this field is 32'768 bytes." } } }, @@ -725,6 +733,10 @@ "type": "string", "format": "uint64", "description": "The smallest amount of asset units that can be transported within a\nsingle HTLC over the Lightning Network with the given rate. This is the\nasset unit equivalent of 354 satoshis, which is the minimum amount for an\nHTLC to be above the dust limit." + }, + "price_oracle_metadata": { + "type": "string", + "description": "An optional user-provided text field used to provide additional metadata\nabout the buy order to the price oracle. This can include information\nabout the wallet end user that initiated the transaction, or any\nauthentication information that the price oracle can use to give out a\nmore accurate (or discount) asset rate." } } }, @@ -777,6 +789,10 @@ "type": "string", "format": "uint64", "description": "The minimum amount of milli-satoshis that need to be sent out in order to\ntransport a single asset unit over the Lightning Network with the given\nrate. This is the base amount of 354,000 milli-satoshi (the minimum\namount for a non-dust HTLC) plus the equivalent of one asset unit in\nmilli-satoshis." + }, + "price_oracle_metadata": { + "type": "string", + "description": "An optional user-provided text field used to provide additional metadata\nabout the sell order to the price oracle. This can include information\nabout the wallet end user that initiated the transaction, or any\nauthentication information that the price oracle can use to give out a\nmore accurate (or discount) asset rate." } } }, diff --git a/taprpc/tapchannelrpc/tapchannel.pb.go b/taprpc/tapchannelrpc/tapchannel.pb.go index 5e2c46fa7..d720f77dc 100644 --- a/taprpc/tapchannelrpc/tapchannel.pb.go +++ b/taprpc/tapchannelrpc/tapchannel.pb.go @@ -400,6 +400,15 @@ type SendPaymentRequest struct { // The group key which dictates which assets may be used for this payment. // Mutually exclusive to asset_id. GroupKey []byte `protobuf:"bytes,7,opt,name=group_key,json=groupKey,proto3" json:"group_key,omitempty"` + // An optional text field that can be used to provide additional metadata + // about the payment to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. Though not verified or enforced + // by tapd, the suggested format for this field is a JSON string. + // This field is optional and can be left empty if no metadata is available. + // The maximum length of this field is 32'768 bytes. + PriceOracleMetadata string `protobuf:"bytes,8,opt,name=price_oracle_metadata,json=priceOracleMetadata,proto3" json:"price_oracle_metadata,omitempty"` } func (x *SendPaymentRequest) Reset() { @@ -483,6 +492,13 @@ func (x *SendPaymentRequest) GetGroupKey() []byte { return nil } +func (x *SendPaymentRequest) GetPriceOracleMetadata() string { + if x != nil { + return x.PriceOracleMetadata + } + return "" +} + type AcceptedSellQuotes struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -725,6 +741,15 @@ type AddInvoiceRequest struct { // invoice. If set, any asset that belongs to this group may be accepted to // settle this invoice. Mutually exclusive to asset_id. GroupKey []byte `protobuf:"bytes,6,opt,name=group_key,json=groupKey,proto3" json:"group_key,omitempty"` + // An optional text field that can be used to provide additional metadata + // about the invoice to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. Though not verified or enforced + // by tapd, the suggested format for this field is a JSON string. + // This field is optional and can be left empty if no metadata is available. + // The maximum length of this field is 32'768 bytes. + PriceOracleMetadata string `protobuf:"bytes,7,opt,name=price_oracle_metadata,json=priceOracleMetadata,proto3" json:"price_oracle_metadata,omitempty"` } func (x *AddInvoiceRequest) Reset() { @@ -801,6 +826,13 @@ func (x *AddInvoiceRequest) GetGroupKey() []byte { return nil } +func (x *AddInvoiceRequest) GetPriceOracleMetadata() string { + if x != nil { + return x.PriceOracleMetadata + } + return "" +} + type AddInvoiceResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -872,6 +904,15 @@ type AssetPayReq struct { // The group key that will be used to resolve the invoice's satoshi amount. // Mutually exclusive to asset_id. GroupKey []byte `protobuf:"bytes,3,opt,name=group_key,json=groupKey,proto3" json:"group_key,omitempty"` + // An optional text field that can be used to provide additional metadata + // about the invoice to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. Though not verified or enforced + // by tapd, the suggested format for this field is a JSON string. + // This field is optional and can be left empty if no metadata is available. + // The maximum length of this field is 32'768 bytes. + PriceOracleMetadata string `protobuf:"bytes,4,opt,name=price_oracle_metadata,json=priceOracleMetadata,proto3" json:"price_oracle_metadata,omitempty"` } func (x *AssetPayReq) Reset() { @@ -927,6 +968,13 @@ func (x *AssetPayReq) GetGroupKey() []byte { return nil } +func (x *AssetPayReq) GetPriceOracleMetadata() string { + if x != nil { + return x.PriceOracleMetadata + } + return "" +} + type AssetPayReqResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1077,7 +1125,7 @@ var file_tapchannelrpc_tapchannel_proto_rawDesc = []byte{ 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x94, 0x02, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xc8, 0x02, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, @@ -1094,118 +1142,128 @@ var file_tapchannelrpc_tapchannel_proto_rawDesc = []byte{ 0x6c, 0x6f, 0x77, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x70, 0x61, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4f, 0x76, 0x65, 0x72, 0x70, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x22, 0x65, 0x0a, 0x12, - 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, - 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x14, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, - 0x65, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, - 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, - 0x12, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x73, 0x22, 0x80, 0x02, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x13, 0x61, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, - 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, - 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x48, 0x00, 0x52, 0x11, 0x61, 0x63, 0x63, 0x65, 0x70, - 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x55, 0x0a, 0x14, - 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x61, 0x70, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, - 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x73, 0x48, 0x00, 0x52, - 0x12, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x73, 0x12, 0x37, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x08, 0x0a, 0x06, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x30, 0x0a, 0x0b, 0x48, 0x6f, 0x64, 0x6c, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0x87, 0x02, 0x0a, 0x11, 0x41, 0x64, 0x64, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, - 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, - 0x70, 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x37, 0x0a, - 0x0f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x0e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, 0x64, 0x6c, 0x5f, 0x69, - 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, - 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x64, - 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x64, 0x6c, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, - 0x65, 0x79, 0x22, 0xa2, 0x01, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x61, 0x63, 0x63, - 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x79, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, - 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, 0x51, 0x75, - 0x6f, 0x74, 0x65, 0x52, 0x10, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, - 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x6b, 0x0a, 0x0b, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, - 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x5f, 0x73, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x52, 0x65, - 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x4b, 0x65, 0x79, 0x22, 0x8e, 0x02, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, - 0x79, 0x52, 0x65, 0x71, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x3f, 0x0a, 0x0f, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, - 0x63, 0x2e, 0x44, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x52, 0x0e, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x12, 0x33, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x36, 0x0a, 0x0c, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, - 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x61, - 0x70, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x26, 0x0a, - 0x07, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, 0x06, 0x70, - 0x61, 0x79, 0x52, 0x65, 0x71, 0x32, 0xdf, 0x03, 0x0a, 0x14, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, - 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x54, - 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x2e, + 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, + 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x69, + 0x63, 0x65, 0x4f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x22, 0x65, 0x0a, 0x12, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, + 0x51, 0x75, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x14, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x65, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, + 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, + 0x6f, 0x74, 0x65, 0x52, 0x12, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, + 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x22, 0x80, 0x02, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4f, 0x0a, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x6c, + 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, + 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x48, 0x00, 0x52, 0x11, 0x61, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x12, 0x55, 0x0a, 0x14, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x6c, + 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, + 0x73, 0x48, 0x00, 0x52, 0x12, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, + 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x37, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, + 0x00, 0x52, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x30, 0x0a, 0x0b, 0x48, 0x6f, + 0x64, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0xbb, 0x02, 0x0a, + 0x11, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, + 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x12, 0x37, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x0e, 0x69, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, + 0x64, 0x6c, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, + 0x2e, 0x48, 0x6f, 0x64, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x0b, 0x68, 0x6f, + 0x64, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, + 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x61, 0x63, + 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xa2, 0x01, 0x0a, 0x12, 0x41, + 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x75, + 0x79, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x10, 0x61, 0x63, 0x63, + 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x40, 0x0a, + 0x0e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, + 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, + 0x9f, 0x01, 0x0a, 0x0b, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, + 0x79, 0x5f, 0x72, 0x65, 0x71, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, + 0x15, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x5f, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x4f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x22, 0x8e, 0x02, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, + 0x71, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x0f, + 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x44, + 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x52, 0x0e, 0x64, + 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x33, 0x0a, + 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x12, 0x36, 0x0a, 0x0c, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x5f, 0x69, 0x6e, + 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x67, + 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x26, 0x0a, 0x07, 0x70, 0x61, + 0x79, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52, + 0x65, 0x71, 0x32, 0xdf, 0x03, 0x0a, 0x14, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x54, 0x0a, 0x0b, 0x46, + 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, - 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, - 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x29, 0x2e, 0x74, 0x61, - 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, - 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x56, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x71, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x29, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x03, 0x88, 0x02, 0x01, 0x12, 0x56, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, - 0x51, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x20, 0x2e, - 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, - 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x53, 0x0a, 0x11, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x1a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, - 0x52, 0x65, 0x71, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, - 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x51, 0x0a, 0x0a, + 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x20, 0x2e, 0x74, 0x61, 0x70, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x74, + 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x53, 0x0a, 0x11, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, + 0x79, 0x52, 0x65, 0x71, 0x12, 0x1a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, + 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, + 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, + 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/taprpc/tapchannelrpc/tapchannel.proto b/taprpc/tapchannelrpc/tapchannel.proto index 7b028767d..8479fb28e 100644 --- a/taprpc/tapchannelrpc/tapchannel.proto +++ b/taprpc/tapchannelrpc/tapchannel.proto @@ -157,6 +157,16 @@ message SendPaymentRequest { // The group key which dictates which assets may be used for this payment. // Mutually exclusive to asset_id. bytes group_key = 7; + + // An optional text field that can be used to provide additional metadata + // about the payment to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. Though not verified or enforced + // by tapd, the suggested format for this field is a JSON string. + // This field is optional and can be left empty if no metadata is available. + // The maximum length of this field is 32'768 bytes. + string price_oracle_metadata = 8; } message AcceptedSellQuotes { @@ -231,6 +241,16 @@ message AddInvoiceRequest { // invoice. If set, any asset that belongs to this group may be accepted to // settle this invoice. Mutually exclusive to asset_id. bytes group_key = 6; + + // An optional text field that can be used to provide additional metadata + // about the invoice to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. Though not verified or enforced + // by tapd, the suggested format for this field is a JSON string. + // This field is optional and can be left empty if no metadata is available. + // The maximum length of this field is 32'768 bytes. + string price_oracle_metadata = 7; } message AddInvoiceResponse { @@ -253,6 +273,16 @@ message AssetPayReq { // The group key that will be used to resolve the invoice's satoshi amount. // Mutually exclusive to asset_id. bytes group_key = 3; + + // An optional text field that can be used to provide additional metadata + // about the invoice to the price oracle. This can include information + // about the wallet end user that initiated the transaction, or any + // authentication information that the price oracle can use to give out a + // more accurate (or discount) asset rate. Though not verified or enforced + // by tapd, the suggested format for this field is a JSON string. + // This field is optional and can be left empty if no metadata is available. + // The maximum length of this field is 32'768 bytes. + string price_oracle_metadata = 4; } message AssetPayReqResponse { diff --git a/taprpc/tapchannelrpc/tapchannel.swagger.json b/taprpc/tapchannelrpc/tapchannel.swagger.json index bf6cb95e5..bf0e8baea 100644 --- a/taprpc/tapchannelrpc/tapchannel.swagger.json +++ b/taprpc/tapchannelrpc/tapchannel.swagger.json @@ -1288,6 +1288,10 @@ "type": "string", "format": "uint64", "description": "The smallest amount of asset units that can be transported within a\nsingle HTLC over the Lightning Network with the given rate. This is the\nasset unit equivalent of 354 satoshis, which is the minimum amount for an\nHTLC to be above the dust limit." + }, + "price_oracle_metadata": { + "type": "string", + "description": "An optional user-provided text field used to provide additional metadata\nabout the buy order to the price oracle. This can include information\nabout the wallet end user that initiated the transaction, or any\nauthentication information that the price oracle can use to give out a\nmore accurate (or discount) asset rate." } } }, @@ -1326,6 +1330,10 @@ "type": "string", "format": "uint64", "description": "The minimum amount of milli-satoshis that need to be sent out in order to\ntransport a single asset unit over the Lightning Network with the given\nrate. This is the base amount of 354,000 milli-satoshi (the minimum\namount for a non-dust HTLC) plus the equivalent of one asset unit in\nmilli-satoshis." + }, + "price_oracle_metadata": { + "type": "string", + "description": "An optional user-provided text field used to provide additional metadata\nabout the sell order to the price oracle. This can include information\nabout the wallet end user that initiated the transaction, or any\nauthentication information that the price oracle can use to give out a\nmore accurate (or discount) asset rate." } } }, @@ -1530,6 +1538,10 @@ "type": "string", "format": "byte", "description": "The group key which dictates which assets may be accepted for this\ninvoice. If set, any asset that belongs to this group may be accepted to\nsettle this invoice. Mutually exclusive to asset_id." + }, + "price_oracle_metadata": { + "type": "string", + "description": "An optional text field that can be used to provide additional metadata\nabout the invoice to the price oracle. This can include information\nabout the wallet end user that initiated the transaction, or any\nauthentication information that the price oracle can use to give out a\nmore accurate (or discount) asset rate. Though not verified or enforced\nby tapd, the suggested format for this field is a JSON string.\nThis field is optional and can be left empty if no metadata is available.\nThe maximum length of this field is 32'768 bytes." } } }, @@ -1562,6 +1574,10 @@ "type": "string", "format": "byte", "description": "The group key that will be used to resolve the invoice's satoshi amount.\nMutually exclusive to asset_id." + }, + "price_oracle_metadata": { + "type": "string", + "description": "An optional text field that can be used to provide additional metadata\nabout the invoice to the price oracle. This can include information\nabout the wallet end user that initiated the transaction, or any\nauthentication information that the price oracle can use to give out a\nmore accurate (or discount) asset rate. Though not verified or enforced\nby tapd, the suggested format for this field is a JSON string.\nThis field is optional and can be left empty if no metadata is available.\nThe maximum length of this field is 32'768 bytes." } } }, @@ -1725,6 +1741,10 @@ "type": "string", "format": "byte", "description": "The group key which dictates which assets may be used for this payment.\nMutually exclusive to asset_id." + }, + "price_oracle_metadata": { + "type": "string", + "description": "An optional text field that can be used to provide additional metadata\nabout the payment to the price oracle. This can include information\nabout the wallet end user that initiated the transaction, or any\nauthentication information that the price oracle can use to give out a\nmore accurate (or discount) asset rate. Though not verified or enforced\nby tapd, the suggested format for this field is a JSON string.\nThis field is optional and can be left empty if no metadata is available.\nThe maximum length of this field is 32'768 bytes." } } },