invoicesrpc

package
v0.12.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 18, 2021 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

Package invoicesrpc is a reverse proxy.

It translates gRPC into RESTful JSON APIs.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddInvoice

func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
	invoice *AddInvoiceData) (*lntypes.Hash, *channeldb.Invoice, error)

AddInvoice attempts to add a new invoice to the invoice database. Any duplicated invoices are rejected, therefore all invoices *must* have a unique payment preimage.

func CreateRPCFeatures

func CreateRPCFeatures(fv *lnwire.FeatureVector) map[uint32]*lnrpc.Feature

CreateRPCFeatures maps a feature vector into a list of lnrpc.Features.

func CreateRPCInvoice

func CreateRPCInvoice(invoice *channeldb.Invoice,
	activeNetParams *chaincfg.Params) (*lnrpc.Invoice, error)

CreateRPCInvoice creates an *lnrpc.Invoice from the *channeldb.Invoice.

func CreateRPCRouteHints

func CreateRPCRouteHints(routeHints [][]zpay32.HopHint) []*lnrpc.RouteHint

CreateRPCRouteHints takes in the decoded form of an invoice's route hints and converts them into the lnrpc type.

func CreateZpay32HopHints

func CreateZpay32HopHints(routeHints []*lnrpc.RouteHint) ([][]zpay32.HopHint, error)

CreateZpay32HopHints takes in the lnrpc form of route hints and converts them into an invoice decoded form.

func DisableLog

func DisableLog()

DisableLog disables all library log output. Logging output is disabled by by default until UseLogger is called.

func RegisterInvoicesHandler

func RegisterInvoicesHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error

RegisterInvoicesHandler registers the http handlers for service Invoices to "mux". The handlers forward requests to the grpc endpoint over "conn".

func RegisterInvoicesHandlerClient

func RegisterInvoicesHandlerClient(ctx context.Context, mux *runtime.ServeMux, client InvoicesClient) error

RegisterInvoicesHandlerClient registers the http handlers for service Invoices to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "InvoicesClient". Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "InvoicesClient" doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in "InvoicesClient" to call the correct interceptors.

func RegisterInvoicesHandlerFromEndpoint

func RegisterInvoicesHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error)

RegisterInvoicesHandlerFromEndpoint is same as RegisterInvoicesHandler but automatically dials to "endpoint" and closes the connection when "ctx" gets done.

func RegisterInvoicesHandlerServer

func RegisterInvoicesHandlerServer(ctx context.Context, mux *runtime.ServeMux, server InvoicesServer) error

RegisterInvoicesHandlerServer registers the http handlers for service Invoices to "mux". UnaryRPC :call InvoicesServer directly. StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.

func RegisterInvoicesServer

func RegisterInvoicesServer(s *grpc.Server, srv InvoicesServer)

func UseLogger

func UseLogger(logger btclog.Logger)

UseLogger uses a specified Logger to output package logging info. This should be used in preference to SetLogWriter if the caller is also using btclog.

Types

type AddHoldInvoiceRequest

type AddHoldInvoiceRequest struct {
	//
	//An optional memo to attach along with the invoice. Used for record keeping
	//purposes for the invoice's creator, and will also be set in the description
	//field of the encoded payment request if the description_hash field is not
	//being used.
	Memo string `protobuf:"bytes,1,opt,name=memo,proto3" json:"memo,omitempty"`
	// The hash of the preimage
	Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"`
	//
	//The value of this invoice in satoshis
	//
	//The fields value and value_msat are mutually exclusive.
	Value int64 `protobuf:"varint,3,opt,name=value,proto3" json:"value,omitempty"`
	//
	//The value of this invoice in millisatoshis
	//
	//The fields value and value_msat are mutually exclusive.
	ValueMsat int64 `protobuf:"varint,10,opt,name=value_msat,json=valueMsat,proto3" json:"value_msat,omitempty"`
	//
	//Hash (SHA-256) of a description of the payment. Used if the description of
	//payment (memo) is too long to naturally fit within the description field
	//of an encoded payment request.
	DescriptionHash []byte `protobuf:"bytes,4,opt,name=description_hash,json=descriptionHash,proto3" json:"description_hash,omitempty"`
	// Payment request expiry time in seconds. Default is 3600 (1 hour).
	Expiry int64 `protobuf:"varint,5,opt,name=expiry,proto3" json:"expiry,omitempty"`
	// Fallback on-chain address.
	FallbackAddr string `protobuf:"bytes,6,opt,name=fallback_addr,json=fallbackAddr,proto3" json:"fallback_addr,omitempty"`
	// Delta to use for the time-lock of the CLTV extended to the final hop.
	CltvExpiry uint64 `protobuf:"varint,7,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"`
	//
	//Route hints that can each be individually used to assist in reaching the
	//invoice's destination.
	RouteHints []*lnrpc.RouteHint `protobuf:"bytes,8,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"`
	// Whether this invoice should include routing hints for private channels.
	Private              bool     `protobuf:"varint,9,opt,name=private,proto3" json:"private,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (*AddHoldInvoiceRequest) Descriptor

func (*AddHoldInvoiceRequest) Descriptor() ([]byte, []int)

func (*AddHoldInvoiceRequest) GetCltvExpiry

func (m *AddHoldInvoiceRequest) GetCltvExpiry() uint64

func (*AddHoldInvoiceRequest) GetDescriptionHash

func (m *AddHoldInvoiceRequest) GetDescriptionHash() []byte

func (*AddHoldInvoiceRequest) GetExpiry

func (m *AddHoldInvoiceRequest) GetExpiry() int64

func (*AddHoldInvoiceRequest) GetFallbackAddr

func (m *AddHoldInvoiceRequest) GetFallbackAddr() string

func (*AddHoldInvoiceRequest) GetHash

func (m *AddHoldInvoiceRequest) GetHash() []byte

func (*AddHoldInvoiceRequest) GetMemo

func (m *AddHoldInvoiceRequest) GetMemo() string

func (*AddHoldInvoiceRequest) GetPrivate

func (m *AddHoldInvoiceRequest) GetPrivate() bool

func (*AddHoldInvoiceRequest) GetRouteHints

func (m *AddHoldInvoiceRequest) GetRouteHints() []*lnrpc.RouteHint

func (*AddHoldInvoiceRequest) GetValue

func (m *AddHoldInvoiceRequest) GetValue() int64

func (*AddHoldInvoiceRequest) GetValueMsat

func (m *AddHoldInvoiceRequest) GetValueMsat() int64

func (*AddHoldInvoiceRequest) ProtoMessage

func (*AddHoldInvoiceRequest) ProtoMessage()

func (*AddHoldInvoiceRequest) Reset

func (m *AddHoldInvoiceRequest) Reset()

func (*AddHoldInvoiceRequest) String

func (m *AddHoldInvoiceRequest) String() string

func (*AddHoldInvoiceRequest) XXX_DiscardUnknown

func (m *AddHoldInvoiceRequest) XXX_DiscardUnknown()

func (*AddHoldInvoiceRequest) XXX_Marshal

func (m *AddHoldInvoiceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*AddHoldInvoiceRequest) XXX_Merge

func (m *AddHoldInvoiceRequest) XXX_Merge(src proto.Message)

func (*AddHoldInvoiceRequest) XXX_Size

func (m *AddHoldInvoiceRequest) XXX_Size() int

func (*AddHoldInvoiceRequest) XXX_Unmarshal

func (m *AddHoldInvoiceRequest) XXX_Unmarshal(b []byte) error

type AddHoldInvoiceResp

type AddHoldInvoiceResp struct {
	//
	//A bare-bones invoice for a payment within the Lightning Network.  With the
	//details of the invoice, the sender has all the data necessary to send a
	//payment to the recipient.
	PaymentRequest       string   `protobuf:"bytes,1,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (*AddHoldInvoiceResp) Descriptor

func (*AddHoldInvoiceResp) Descriptor() ([]byte, []int)

func (*AddHoldInvoiceResp) GetPaymentRequest

func (m *AddHoldInvoiceResp) GetPaymentRequest() string

func (*AddHoldInvoiceResp) ProtoMessage

func (*AddHoldInvoiceResp) ProtoMessage()

func (*AddHoldInvoiceResp) Reset

func (m *AddHoldInvoiceResp) Reset()

func (*AddHoldInvoiceResp) String

func (m *AddHoldInvoiceResp) String() string

func (*AddHoldInvoiceResp) XXX_DiscardUnknown

func (m *AddHoldInvoiceResp) XXX_DiscardUnknown()

func (*AddHoldInvoiceResp) XXX_Marshal

func (m *AddHoldInvoiceResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*AddHoldInvoiceResp) XXX_Merge

func (m *AddHoldInvoiceResp) XXX_Merge(src proto.Message)

func (*AddHoldInvoiceResp) XXX_Size

func (m *AddHoldInvoiceResp) XXX_Size() int

func (*AddHoldInvoiceResp) XXX_Unmarshal

func (m *AddHoldInvoiceResp) XXX_Unmarshal(b []byte) error

type AddInvoiceConfig

type AddInvoiceConfig struct {
	// AddInvoice is called to add the invoice to the registry.
	AddInvoice func(invoice *channeldb.Invoice, paymentHash lntypes.Hash) (
		uint64, error)

	// IsChannelActive is used to generate valid hop hints.
	IsChannelActive func(chanID lnwire.ChannelID) bool

	// ChainParams are required to properly decode invoice payment requests
	// that are marshalled over rpc.
	ChainParams *chaincfg.Params

	// NodeSigner is an implementation of the MessageSigner implementation
	// that's backed by the identity private key of the running lnd node.
	NodeSigner *netann.NodeSigner

	// DefaultCLTVExpiry is the default invoice expiry if no values is
	// specified.
	DefaultCLTVExpiry uint32

	// ChanDB is a global boltdb instance which is needed to access the
	// channel graph.
	ChanDB *channeldb.DB

	// Graph holds a reference to the ChannelGraph database.
	Graph *channeldb.ChannelGraph

	// GenInvoiceFeatures returns a feature containing feature bits that
	// should be advertised on freshly generated invoices.
	GenInvoiceFeatures func() *lnwire.FeatureVector
}

AddInvoiceConfig contains dependencies for invoice creation.

type AddInvoiceData

type AddInvoiceData struct {
	// An optional memo to attach along with the invoice. Used for record
	// keeping purposes for the invoice's creator, and will also be set in
	// the description field of the encoded payment request if the
	// description_hash field is not being used.
	Memo string

	// The preimage which will allow settling an incoming HTLC payable to
	// this preimage. If Preimage is set, Hash should be nil. If both
	// Preimage and Hash are nil, a random preimage is generated.
	Preimage *lntypes.Preimage

	// The hash of the preimage. If Hash is set, Preimage should be nil.
	// This condition indicates that we have a 'hold invoice' for which the
	// htlc will be accepted and held until the preimage becomes known.
	Hash *lntypes.Hash

	// The value of this invoice in millisatoshis.
	Value lnwire.MilliSatoshi

	// Hash (SHA-256) of a description of the payment. Used if the
	// description of payment (memo) is too long to naturally fit within the
	// description field of an encoded payment request.
	DescriptionHash []byte

	// Payment request expiry time in seconds. Default is 3600 (1 hour).
	Expiry int64

	// Fallback on-chain address.
	FallbackAddr string

	// Delta to use for the time-lock of the CLTV extended to the final hop.
	CltvExpiry uint64

	// Whether this invoice should include routing hints for private
	// channels.
	Private bool

	// HodlInvoice signals that this invoice shouldn't be settled
	// immediately upon receiving the payment.
	HodlInvoice bool

	// RouteHints are optional route hints that can each be individually used
	// to assist in reaching the invoice's destination.
	RouteHints [][]zpay32.HopHint
}

AddInvoiceData contains the required data to create a new invoice.

type CancelInvoiceMsg

type CancelInvoiceMsg struct {
	// Hash corresponding to the (hold) invoice to cancel.
	PaymentHash          []byte   `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (*CancelInvoiceMsg) Descriptor

func (*CancelInvoiceMsg) Descriptor() ([]byte, []int)

func (*CancelInvoiceMsg) GetPaymentHash

func (m *CancelInvoiceMsg) GetPaymentHash() []byte

func (*CancelInvoiceMsg) ProtoMessage

func (*CancelInvoiceMsg) ProtoMessage()

func (*CancelInvoiceMsg) Reset

func (m *CancelInvoiceMsg) Reset()

func (*CancelInvoiceMsg) String

func (m *CancelInvoiceMsg) String() string

func (*CancelInvoiceMsg) XXX_DiscardUnknown

func (m *CancelInvoiceMsg) XXX_DiscardUnknown()

func (*CancelInvoiceMsg) XXX_Marshal

func (m *CancelInvoiceMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*CancelInvoiceMsg) XXX_Merge

func (m *CancelInvoiceMsg) XXX_Merge(src proto.Message)

func (*CancelInvoiceMsg) XXX_Size

func (m *CancelInvoiceMsg) XXX_Size() int

func (*CancelInvoiceMsg) XXX_Unmarshal

func (m *CancelInvoiceMsg) XXX_Unmarshal(b []byte) error

type CancelInvoiceResp

type CancelInvoiceResp struct {
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (*CancelInvoiceResp) Descriptor

func (*CancelInvoiceResp) Descriptor() ([]byte, []int)

func (*CancelInvoiceResp) ProtoMessage

func (*CancelInvoiceResp) ProtoMessage()

func (*CancelInvoiceResp) Reset

func (m *CancelInvoiceResp) Reset()

func (*CancelInvoiceResp) String

func (m *CancelInvoiceResp) String() string

func (*CancelInvoiceResp) XXX_DiscardUnknown

func (m *CancelInvoiceResp) XXX_DiscardUnknown()

func (*CancelInvoiceResp) XXX_Marshal

func (m *CancelInvoiceResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*CancelInvoiceResp) XXX_Merge

func (m *CancelInvoiceResp) XXX_Merge(src proto.Message)

func (*CancelInvoiceResp) XXX_Size

func (m *CancelInvoiceResp) XXX_Size() int

func (*CancelInvoiceResp) XXX_Unmarshal

func (m *CancelInvoiceResp) XXX_Unmarshal(b []byte) error

type Config

type Config struct{}

Config is empty for non-invoicesrpc builds.

type InvoicesClient

type InvoicesClient interface {
	//
	//SubscribeSingleInvoice returns a uni-directional stream (server -> client)
	//to notify the client of state transitions of the specified invoice.
	//Initially the current invoice state is always sent out.
	SubscribeSingleInvoice(ctx context.Context, in *SubscribeSingleInvoiceRequest, opts ...grpc.CallOption) (Invoices_SubscribeSingleInvoiceClient, error)
	//
	//CancelInvoice cancels a currently open invoice. If the invoice is already
	//canceled, this call will succeed. If the invoice is already settled, it will
	//fail.
	CancelInvoice(ctx context.Context, in *CancelInvoiceMsg, opts ...grpc.CallOption) (*CancelInvoiceResp, error)
	//
	//AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
	//supplied in the request.
	AddHoldInvoice(ctx context.Context, in *AddHoldInvoiceRequest, opts ...grpc.CallOption) (*AddHoldInvoiceResp, error)
	//
	//SettleInvoice settles an accepted invoice. If the invoice is already
	//settled, this call will succeed.
	SettleInvoice(ctx context.Context, in *SettleInvoiceMsg, opts ...grpc.CallOption) (*SettleInvoiceResp, error)
}

InvoicesClient is the client API for Invoices service.

For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.

func NewInvoicesClient

func NewInvoicesClient(cc *grpc.ClientConn) InvoicesClient

type InvoicesServer

type InvoicesServer interface {
	//
	//SubscribeSingleInvoice returns a uni-directional stream (server -> client)
	//to notify the client of state transitions of the specified invoice.
	//Initially the current invoice state is always sent out.
	SubscribeSingleInvoice(*SubscribeSingleInvoiceRequest, Invoices_SubscribeSingleInvoiceServer) error
	//
	//CancelInvoice cancels a currently open invoice. If the invoice is already
	//canceled, this call will succeed. If the invoice is already settled, it will
	//fail.
	CancelInvoice(context.Context, *CancelInvoiceMsg) (*CancelInvoiceResp, error)
	//
	//AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
	//supplied in the request.
	AddHoldInvoice(context.Context, *AddHoldInvoiceRequest) (*AddHoldInvoiceResp, error)
	//
	//SettleInvoice settles an accepted invoice. If the invoice is already
	//settled, this call will succeed.
	SettleInvoice(context.Context, *SettleInvoiceMsg) (*SettleInvoiceResp, error)
}

InvoicesServer is the server API for Invoices service.

type Invoices_SubscribeSingleInvoiceClient

type Invoices_SubscribeSingleInvoiceClient interface {
	Recv() (*lnrpc.Invoice, error)
	grpc.ClientStream
}

type Invoices_SubscribeSingleInvoiceServer

type Invoices_SubscribeSingleInvoiceServer interface {
	Send(*lnrpc.Invoice) error
	grpc.ServerStream
}

type SettleInvoiceMsg

type SettleInvoiceMsg struct {
	// Externally discovered pre-image that should be used to settle the hold
	// invoice.
	Preimage             []byte   `protobuf:"bytes,1,opt,name=preimage,proto3" json:"preimage,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (*SettleInvoiceMsg) Descriptor

func (*SettleInvoiceMsg) Descriptor() ([]byte, []int)

func (*SettleInvoiceMsg) GetPreimage

func (m *SettleInvoiceMsg) GetPreimage() []byte

func (*SettleInvoiceMsg) ProtoMessage

func (*SettleInvoiceMsg) ProtoMessage()

func (*SettleInvoiceMsg) Reset

func (m *SettleInvoiceMsg) Reset()

func (*SettleInvoiceMsg) String

func (m *SettleInvoiceMsg) String() string

func (*SettleInvoiceMsg) XXX_DiscardUnknown

func (m *SettleInvoiceMsg) XXX_DiscardUnknown()

func (*SettleInvoiceMsg) XXX_Marshal

func (m *SettleInvoiceMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*SettleInvoiceMsg) XXX_Merge

func (m *SettleInvoiceMsg) XXX_Merge(src proto.Message)

func (*SettleInvoiceMsg) XXX_Size

func (m *SettleInvoiceMsg) XXX_Size() int

func (*SettleInvoiceMsg) XXX_Unmarshal

func (m *SettleInvoiceMsg) XXX_Unmarshal(b []byte) error

type SettleInvoiceResp

type SettleInvoiceResp struct {
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (*SettleInvoiceResp) Descriptor

func (*SettleInvoiceResp) Descriptor() ([]byte, []int)

func (*SettleInvoiceResp) ProtoMessage

func (*SettleInvoiceResp) ProtoMessage()

func (*SettleInvoiceResp) Reset

func (m *SettleInvoiceResp) Reset()

func (*SettleInvoiceResp) String

func (m *SettleInvoiceResp) String() string

func (*SettleInvoiceResp) XXX_DiscardUnknown

func (m *SettleInvoiceResp) XXX_DiscardUnknown()

func (*SettleInvoiceResp) XXX_Marshal

func (m *SettleInvoiceResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*SettleInvoiceResp) XXX_Merge

func (m *SettleInvoiceResp) XXX_Merge(src proto.Message)

func (*SettleInvoiceResp) XXX_Size

func (m *SettleInvoiceResp) XXX_Size() int

func (*SettleInvoiceResp) XXX_Unmarshal

func (m *SettleInvoiceResp) XXX_Unmarshal(b []byte) error

type SubscribeSingleInvoiceRequest

type SubscribeSingleInvoiceRequest struct {
	// Hash corresponding to the (hold) invoice to subscribe to.
	RHash                []byte   `protobuf:"bytes,2,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (*SubscribeSingleInvoiceRequest) Descriptor

func (*SubscribeSingleInvoiceRequest) Descriptor() ([]byte, []int)

func (*SubscribeSingleInvoiceRequest) GetRHash

func (m *SubscribeSingleInvoiceRequest) GetRHash() []byte

func (*SubscribeSingleInvoiceRequest) ProtoMessage

func (*SubscribeSingleInvoiceRequest) ProtoMessage()

func (*SubscribeSingleInvoiceRequest) Reset

func (m *SubscribeSingleInvoiceRequest) Reset()

func (*SubscribeSingleInvoiceRequest) String

func (*SubscribeSingleInvoiceRequest) XXX_DiscardUnknown

func (m *SubscribeSingleInvoiceRequest) XXX_DiscardUnknown()

func (*SubscribeSingleInvoiceRequest) XXX_Marshal

func (m *SubscribeSingleInvoiceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*SubscribeSingleInvoiceRequest) XXX_Merge

func (m *SubscribeSingleInvoiceRequest) XXX_Merge(src proto.Message)

func (*SubscribeSingleInvoiceRequest) XXX_Size

func (m *SubscribeSingleInvoiceRequest) XXX_Size() int

func (*SubscribeSingleInvoiceRequest) XXX_Unmarshal

func (m *SubscribeSingleInvoiceRequest) XXX_Unmarshal(b []byte) error

type UnimplementedInvoicesServer

type UnimplementedInvoicesServer struct {
}

UnimplementedInvoicesServer can be embedded to have forward compatible implementations.

func (*UnimplementedInvoicesServer) AddHoldInvoice

func (*UnimplementedInvoicesServer) CancelInvoice

func (*UnimplementedInvoicesServer) SettleInvoice

func (*UnimplementedInvoicesServer) SubscribeSingleInvoice

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL