Documentation ¶
Overview ¶
Package wos leverages the Wallet Of Satoshi REST API to process Bitcoin payments.
Wallet Of Satoshi is a custodial bitcoin wallet which is reknowned for its ease of use. WoS does not provide any documentation on their API, so this package is mostly reverse-engineered by studying how the WoS API is used in the wild.
See the README for more info.
Index ¶
- Constants
- Variables
- func CreateWallet(ctx context.Context, httpClient *http.Client) (*Wallet, *Credentials, error)
- type Addresses
- type Balance
- type Credentials
- type FeeEstimate
- type Invoice
- type InvoiceOptions
- type LightningAddress
- type Payment
- type PaymentCurrency
- type PaymentStatus
- type PaymentType
- type Reader
- func (rdr *Reader) Addresses(ctx context.Context) (*Addresses, error)
- func (rdr *Reader) Balance(ctx context.Context) (*Balance, error)
- func (rdr *Reader) BalanceAndFee(ctx context.Context, addressOrInvoice string) (*Balance, *FeeEstimate, error)
- func (rdr *Reader) FeeEstimate(ctx context.Context, addressOrInvoice string) (*FeeEstimate, error)
- func (rdr *Reader) GetRequest(ctx context.Context, endpoint string) ([]byte, error)
- func (rdr *Reader) ListPayments(ctx context.Context) ([]Payment, error)
- type Signer
- type SimpleSigner
- type Wallet
- func (wallet *Wallet) Addresses(ctx context.Context) (*Addresses, error)
- func (wallet *Wallet) Balance(ctx context.Context) (*Balance, error)
- func (wallet *Wallet) FeeEstimate(ctx context.Context, addressOrInvoice string) (*FeeEstimate, error)
- func (wallet *Wallet) LightningAddress() LightningAddress
- func (wallet *Wallet) NewInvoice(ctx context.Context, opts *InvoiceOptions) (*Invoice, error)
- func (wallet *Wallet) OnChainAddress() string
- func (wallet *Wallet) PayInvoice(ctx context.Context, invoice, description string) (*Payment, error)
- func (wallet *Wallet) PayLightningAddress(ctx context.Context, lnAddress LightningAddress, description string, ...) (*Payment, error)
- func (wallet *Wallet) PayOnChain(ctx context.Context, address string, amount float64, description string) (*Payment, error)
- func (wallet *Wallet) PayVariableInvoice(ctx context.Context, invoice string, description string, amount float64) (*Payment, error)
- func (wallet *Wallet) PostRequest(ctx context.Context, endpoint string, body any) ([]byte, error)
- func (wallet *Wallet) SetHTTPClient(httpClient *http.Client)
- func (wallet *Wallet) SweepLightning(ctx context.Context, invoice, description string) (*Payment, error)
- func (wallet *Wallet) SweepOnChain(ctx context.Context, address, description string) (*Payment, error)
Examples ¶
Constants ¶
const ( PaymentStatusPaid PaymentStatus = "PAID" // The payment has been completed and confirmed. PaymentStatusPending PaymentStatus = "PENDING" // An on-chain payment is still confirming. PaymentTypeCredit PaymentType = "CREDIT" // A received payment. PaymentTypeDebit PaymentType = "DEBIT" // A sent payment. PaymentCurrencyBitcoin PaymentCurrency = "BTC" // On-chain bitcoin. PaymentCurrencyLightning PaymentCurrency = "LIGHTNING" // Off-chain lightning network credit. )
const BaseURL = "https://www.livingroomofsatoshi.com"
BaseURL is the API URL for the Wallet of Satoshi API.
Variables ¶
var ( // ErrInvalidInvoice is returned when an invalid invoice is received. ErrInvalidInvoice = errors.New("invalid invoice") // ErrNoAmount is returned when paying an invoice which has no amount specified. ErrNoAmount = errors.New("no amount specified in invoice") // ErrFixedAmount is returned when attempting to pay a fixed-amount invoice // as if it were a variable amount invoice. ErrFixedAmount = errors.New("invoice specifies a fixed amount") )
var ErrInvalidLightningAddress = errors.New("invalid lightning address")
ErrInvalidLightningAddress is returned when parsing an invalid lightning address.
var ErrOutsideSendableRange = errors.New("amount to send to LN address is outside the recipient's accepted range")
ErrOutsideSendableRange is returned when sending to a lightning address, but the amount the caller asks to send is outside the range accepted by the receiver.
Functions ¶
func CreateWallet ¶
CreateWallet asks the WoS API to create a brand new wallet from scratch. It returns a Wallet which can be used right away, and a set of access Credentials which should be saved in a persistent storage medium so that the wallet can be re-opened later with OpenWallet.
Example ¶
package main import ( "context" "fmt" "github.com/conduition/wos" ) func main() { wallet, creds, err := wos.CreateWallet(context.Background(), nil) if err != nil { panic(err) } fmt.Println(wallet.LightningAddress()) fmt.Printf("API token: %s\n", creds.APIToken) fmt.Printf("API secret: %s\n", creds.APISecret) }
Output:
Types ¶
type Addresses ¶
type Addresses struct { OnChain string `json:"btcDepositAddress"` Lightning string `json:"lightningAddress"` }
Addresses represents the on-chain and lightning deposit addresses for a Wallet.
type Credentials ¶
type Credentials struct { // APISecret is a base58 secret needed for write-access to a wallet. // This secret is used to sign any requests to POST endpoints, such // as those which create invoices and make payments. // // APISecret is useless on its own, but if the corresponding APIToken is // available, it permits full access to spend funds from a WoS wallet. APISecret string `json:"apiSecret"` // APIToken is a read-only access token used to fetch balance information, // estimate fees, and view the transaction history of a wallet. // // Without the APISecret, it can only be used to view a WoS wallet, but // it cannot modify the wallet's state in any way. APIToken string `json:"apiToken"` }
Credentials represents a full set of credentials for a WoS wallet.
func (Credentials) OpenWallet ¶
func (creds Credentials) OpenWallet( ctx context.Context, httpClient *http.Client, ) (*Wallet, error)
OpenWallet opens a Wallet using the given http.Client for all API calls.
Example ¶
package main import ( "context" "fmt" "github.com/conduition/wos" ) func main() { creds := wos.Credentials{ APIToken: "edcc867c-96ff-4b0d-ba68-165c16071de0", APISecret: "91ul0rDKV1gANhQWWyEXhdWaSa6aQwAF", } wallet, err := creds.OpenWallet(context.Background(), nil) if err != nil { panic(err) } fmt.Println(wallet.LightningAddress().String()) }
Output: dorsalpuma54@walletofsatoshi.com
func (Credentials) Reader ¶
func (creds Credentials) Reader(httpClient *http.Client) *Reader
Reader builds Generate a Reader object from the APIToken.
HTTP API calls made by the reader will be executed by the given http.Client.
func (Credentials) SimpleSigner ¶
func (creds Credentials) SimpleSigner() *SimpleSigner
SimpleSigner returns a SimpleSigner which signs using the APISecret.
type FeeEstimate ¶
type FeeEstimate struct { BtcFixedFee float64 `json:"btcFixedFee"` BtcMinerFeePerKB float64 `json:"btcMinerFeePerKb"` BtcSendCommissionPercent float64 `json:"btcSendCommissionPercent"` BtcSendFeeWarningPercent float64 `json:"btcSendFeeWarningPercent"` LightningFee float64 `json:"lightningFee"` MaxLightningFee float64 `json:"sendMaxLightningFee"` IsWosInvoice bool `json:"wosInvoice"` }
type Invoice ¶
type Invoice struct { // ID is a UUID which identifies the invoice. ID string `json:"id"` // Bolt11 is the [BOLT11] serialized invoice. This is what should // be displayed to the user or encoded in QR codes. Bolt11 string `json:"invoice"` // Amount is the Bitcoin amount encoded in the invoice. Amount float64 `json:"btcAmount"` // Expires is the expiry time at which the invoice is no longer payable. Expires time.Time `json:"expires"` }
Invoice is a Bitcoin Lightning invoice returned by the WoS API.
type InvoiceOptions ¶
type InvoiceOptions struct { // Amount is the Bitcoin-denominated amount for the invoice. If not specified, // the payee can decide how much to pay. Amount float64 // Description to include in the invoice for the payee. If omitted, // no description will be provided to the payee. Description string // The expiry time for the invoice, after which it can no longer be paid. // If omitted, defaults to 24 hours. Expiry time.Duration }
InvoiceOptions is used to customize invoices created by Wallet.NewInvoice.
type LightningAddress ¶
LightningAddress is a `user@domain.tld` internet identifier which allows senders to request lightning invoices by contacting `domain.tld`, who issues invoices on behalf of the `user`.
func ParseLightningAddress ¶
func ParseLightningAddress(lnAddress string) (LightningAddress, error)
ParseLightningAddress parses a LightningAddress from a string, returning ErrInvalidLightningAddress if the address is not a valid identifier.
func (LightningAddress) LNURL ¶
func (a LightningAddress) LNURL() string
LNURL returns the HTTPS URL used for LNURL payRequest, as per LUD-16.
func (LightningAddress) String ¶
func (a LightningAddress) String() string
String returns the user@domain.tld format of the address.
type Payment ¶
type Payment struct { // A UUID identifying the payment. ID string `json:"id"` // For on-chain bitcoin, this is the address the payment was sent to. // For lightning, this is the invoice or LN address. Address string `json:"address"` // Amount is the Bitcoin-denominated amount of the payment. Amount float64 `json:"amount"` // Currency is either PaymentCurrencyBitcoin or PaymentCurrencyLightning. Currency PaymentCurrency `json:"currency"` // Invoice description, or empty string otherwise. Description string `json:"description"` // Invoice expiry time. Empty for debits. Expires time.Time `json:"expires"` // If WoS thinks this payment is spam. IsLikelySpam bool `json:"isLikelySpam"` // If the payment came from the WOS point-of-sale system. IsPointOfSale bool `json:"isWosPos"` // Time the payment occurred. Time time.Time `json:"time"` // For lightning payments, this is the payment hash. Txid string `json:"transactionId"` // Status is either PaymentStatusPaid or PaymentStatusPending. // More statuses may exist. Status PaymentStatus `json:"status"` // Type is either PaymentTypeCredit or PaymentTypeDebit. Type PaymentType `json:"type"` }
Payment represents an on-chain or lightning payment, either received or sent.
type PaymentCurrency ¶
type PaymentCurrency string
PaymentCurrency indicates whether a payment was made on-chain or via Lightning.
type PaymentType ¶
type PaymentType string
PaymentType indicates whether a payment was incoming or outgoing from a wallet.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader facilitates read-only access to a WoS wallet. It can be used to fetch balances, payment history, and estimate fees.
Example ¶
package main import ( "context" "fmt" "github.com/conduition/wos" ) func main() { reader := wos.NewReader("edcc867c-96ff-4b0d-ba68-165c16071de0", nil) addresses, err := reader.Addresses(context.Background()) if err != nil { panic(err) } fmt.Println(addresses.Lightning) }
Output: dorsalpuma54@walletofsatoshi.com
func NewReader ¶
NewReader constructs a Reader from a given http.Client and read-only apiToken.
Uses http.DefaultClient if httpClient is nil.
func (*Reader) Addresses ¶
Addresses re-fetches the wallet's on-chain and lightning addresses. This can be useful to ensure you have the wallet's latest unused on-chain deposit address.
func (*Reader) Balance ¶
Balance returns the current confirmed and unconfirmed balances of the wallet.
func (*Reader) BalanceAndFee ¶
func (*Reader) FeeEstimate ¶
FeeEstimate fetches the latest fee estimation data when paying to a given on-chain address or lightning invoice.
func (*Reader) GetRequest ¶
GetRequest issues a GET request to the given endpoint, authenticated with the Reader's API token.
type Signer ¶
type Signer interface { // SignRequest should return a SHA256 HMAC on the following string: // // endpoint + nonce + apiToken + requestBody // // SignRequest may also perform validation or introspection on the request // and decide whether to sign it. // // If the Signer does not want to sign the request, it should return an error // which will be wrapped and bubbled up to the higher-level [Wallet] method. SignRequest(ctx context.Context, endpoint, nonce, apiToken, requestBody string) ([]byte, error) }
Signer represents an HMAC-SHA256 signer which signs the given HTTP request details using the APISecret from the WoS Credentials.
For a simple instantiation of Signer, see SimpleSigner.
Example ¶
package main import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "github.com/conduition/wos" ) type RemoteSigner struct { URL string } func (rs RemoteSigner) SignRequest( ctx context.Context, endpoint, nonce, requestBody, apiToken string, ) ([]byte, error) { bodyBytes, err := json.Marshal(map[string]string{ "endpoint": endpoint, "nonce": nonce, "body": requestBody, }) if err != nil { return nil, err } req, err := http.NewRequestWithContext(ctx, "POST", rs.URL, bytes.NewReader(bodyBytes)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("received status code %d from remote signer", resp.StatusCode) } return io.ReadAll(resp.Body) } func main() { reader := wos.NewReader("93b9c574-30a2-4bf5-81ba-f9feadb313a7", nil) signer := RemoteSigner{"https://somewheresecure.place/api/sign"} wallet, err := wos.OpenWallet(context.Background(), reader, signer) if err != nil { panic(err) } fmt.Println(wallet.LightningAddress()) }
Output:
type SimpleSigner ¶
type SimpleSigner struct {
// contains filtered or unexported fields
}
SimpleSigner implements Signer with a static secret and no validation.
Use Credentials.SimpleSigner to create a SimpleSigner from a set of credentials.
func NewSimpleSigner ¶
func NewSimpleSigner(apiSecret string) *SimpleSigner
NewSimpleSigner creates a SimpleSigner from a given APISecret
func (*SimpleSigner) SignRequest ¶
func (s *SimpleSigner) SignRequest( ctx context.Context, endpoint, nonce, apiToken, requestBody string, ) ([]byte, error)
SignRequest implements Signer.
type Wallet ¶
type Wallet struct {
// contains filtered or unexported fields
}
Wallet represents a Wallet of Satoshi wallet, including the mechanisms needed to read its history and balances, create invoices, and make payments.
To create a brand new wallet from scratch, use CreateWallet.
To open an existing wallet from a set of Credentials, use Credentials.OpenWallet.
To open a wallet from an isolated signing mechanism, use OpenWallet with a given Signer.
func OpenWallet ¶
OpenWallet opens an existing wallet using a separate Reader and Signer.
The Reader will be used to fetch read-only information about the wallet, while the Signer authenticates write calls.
func (*Wallet) Addresses ¶
Addresses re-fetches the wallet's on-chain and lightning addresses. This can be useful to ensure you have the wallet's latest unused on-chain deposit address.
func (*Wallet) Balance ¶
Balance returns the current confirmed and unconfirmed balances of the wallet.
func (*Wallet) FeeEstimate ¶
func (wallet *Wallet) FeeEstimate(ctx context.Context, addressOrInvoice string) (*FeeEstimate, error)
FeeEstimate fetches the latest fee estimation data when paying to a given on-chain address or lightning invoice.
func (*Wallet) LightningAddress ¶
func (wallet *Wallet) LightningAddress() LightningAddress
LightningAddress returns the wallet's static Lightning Address.
func (*Wallet) NewInvoice ¶
NewInvoice creates a new BOLT11 payment invoice, essentially a request for payment.
The InvoiceOptions argument customizes the invoice. opts can be nil, which creates a variable-amount invoice with no description and a 24-hour expiry.
func (*Wallet) OnChainAddress ¶
OnChainAddress returns the wallet's on-chain deposit address. Be aware this address might be reused, which is sub-optimal for privacy. To fetch an up-to-date address, use Wallet.Addresses, or re-open the wallet.
func (*Wallet) PayInvoice ¶
func (wallet *Wallet) PayInvoice(ctx context.Context, invoice, description string) (*Payment, error)
PayInvoice executes a payment to a given lightning invoice. The description is stored in the WoS payment history.
Returns an error wrapping ErrInvalidInvoice if the invoice is not valid.
If the invoice does not specify a fixed amount, this method returns an error wrapping ErrNoAmount. To pay a variable-amount invoice, use Wallet.PayVariableInvoice.
To estimate fees, use Wallet.FeeEstimate or Reader.FeeEstimate.
func (*Wallet) PayLightningAddress ¶
func (wallet *Wallet) PayLightningAddress( ctx context.Context, lnAddress LightningAddress, description string, amount float64, ) (*Payment, error)
PayLightningAddress executes a payment of the given BTC amount to a given lightning address. The description is stored in the WoS payment history.
Returns ErrOutsideSendableRange if the amount to be sent is outside the receiver's acceptable min/max sendable range.
Under the hood, this uses the WoS API to proxy your request to [LightningAddress.Domain], so that the recipient does not see your IP address.
Example ¶
package main import ( "context" "fmt" "github.com/conduition/wos" ) func main() { creds := wos.Credentials{ APIToken: "edcc867c-96ff-4b0d-ba68-165c16071de0", APISecret: "91ul0rDKV1gANhQWWyEXhdWaSa6aQwAF", } ctx := context.Background() wallet, err := creds.OpenWallet(ctx, nil) if err != nil { panic(err) } // smallwillow98@walletofsatoshi.com // API token: 6edf02b8-d4e9-4640-b7e4-90bc97f476ab // API secret: sgN5hn2RibvSba1vv260NvwnwVy0oiuh lnAddress, err := wos.ParseLightningAddress("smallwillow98@walletofsatoshi.com") if err != nil { panic(err) } payment, err := wallet.PayLightningAddress(ctx, lnAddress, "", 0.00000001) if err != nil { panic(err) } fmt.Println(payment.Status) }
Output:
func (*Wallet) PayOnChain ¶
func (wallet *Wallet) PayOnChain( ctx context.Context, address string, amount float64, description string, ) (*Payment, error)
PayOnChain executes an on-chain payment transaction, paying amount to the given address. The description is stored in the WoS payment history.
To estimate fees, use Wallet.FeeEstimate or Reader.FeeEstimate.
func (*Wallet) PayVariableInvoice ¶
func (wallet *Wallet) PayVariableInvoice( ctx context.Context, invoice string, description string, amount float64, ) (*Payment, error)
PayVariableInvoice executes a payment to a given variable-amount lightning invoice. The description is stored in the WoS payment history.
Returns an error wrapping ErrInvalidInvoice if the invoice is not valid.
Returns an error wrapping ErrFixedAmount if the invoice specifies a fixed amount. In this case, you should use Wallet.PayInvoice.
To estimate fees, use Wallet.FeeEstimate or Reader.FeeEstimate.
func (*Wallet) PostRequest ¶
PostRequest issues an HTTP POST request to the given endpoint, authenticated by the Wallet's internal Signer. The body parameter is marshaled to JSON and sent as the request body.
func (*Wallet) SetHTTPClient ¶
SetHTTPClient updates the http.Client used by the wallet and its internal Reader.
func (*Wallet) SweepLightning ¶
func (wallet *Wallet) SweepLightning(ctx context.Context, invoice, description string) (*Payment, error)
SweepLightning executes a lightning payment, sweeping the entire available lightning balance to a given variable-amount invoice. The description is stored in the WoS payment history.
Returns an error wrapping ErrInvalidInvoice if the invoice is not valid.
Returns an error wrapping ErrFixedAmount if the invoice embeds a fixed amount.
func (*Wallet) SweepOnChain ¶
func (wallet *Wallet) SweepOnChain(ctx context.Context, address, description string) (*Payment, error)
SweepOnChain executes an on-chain payment transaction, sweeping the entire available wallet balance to a given on-chain address. The description is stored in the WoS payment history.