Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ handover*.md
ulw-*.md
# Captive portal build output (built from source via packaging/portal-build.sh)
packaging/files/tollgate-captive-portal-site/assets/
nuts/
21 changes: 21 additions & 0 deletions specquotes.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# greatspectations spec-quote drift config for tollgate-module-basic-go
#
# Tool: https://github.com/rustyrussell/greatspectations
# Spec: https://github.com/cashubtc/nuts (Cashu NUT specifications)
#
# Comment marker syntax in Go source:
# // NUT #00: <verbatim spec quote>
# // NUT #05: <verbatim spec quote>
#
# Setup (one-time, local):
# git clone --depth=1 https://github.com/cashubtc/nuts.git nuts
# pip install git+https://github.com/rustyrussell/greatspectations.git
#
# Run:
# spectate check --config specquotes.toml --comment-start '//' --comment-continue '//' 'src/**/*.go'
# spectate coverage --config specquotes.toml

[sources.NUT]
format = "markdown"
dir = "nuts"
pattern = "{id:02d}.md"
3 changes: 3 additions & 0 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,8 @@ func handleDetails(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, merchantProvider.inner.GetMerchant().GetAdvertisement())
}

// NUT #00: `cashu` is the Cashu token prefix. `[version]` is a single `base64_urlsafe` character to denote the token format version.

// handleRootPost handles POST requests to the root endpoint
func extractCashuToken(body []byte) (token string, event *nostr.Event) {
var ev nostr.Event
Expand All @@ -422,6 +424,7 @@ func extractCashuToken(body []byte) (token string, event *nostr.Event) {
return strings.TrimSpace(string(body)), nil
}

// NUT #00: Serialized tokens have a Cashu token prefix, a versioning flag, and the token.
func HandleRootPost(w http.ResponseWriter, r *http.Request) {
// Log the request details
mainLogger.WithFields(logrus.Fields{
Expand Down
1 change: 1 addition & 0 deletions src/merchant/merchant.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ type PurchaseSessionResult struct {
}

// PurchaseSession processes a payment with cashu token and MAC address, returns either a session event or a notice event
// NUT-00 verification quote lives above TollWallet.Receive (single source; duplicate quotes flag in speccheck).
func (m *Merchant) PurchaseSession(cashuToken string, macAddress string) (*nostr.Event, error) {
// Validate MAC address
if !utils.ValidateMACAddress(macAddress) {
Expand Down
7 changes: 7 additions & 0 deletions src/tollwallet/tollwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func (w *TollWallet) Shutdown() error {
return nil
}

// NUT #00: `Carol` can send `(x, C)` to `Bob` who then checks that `k*hash_to_curve(x) == C` (**verification**), and if so treats it as a valid spend of a token, adding `x` to the list of spent secrets.
func (w *TollWallet) Receive(token cashu.Token) (uint64, error) {
mint := token.Mint()

Expand Down Expand Up @@ -151,6 +152,7 @@ func (w *TollWallet) Receive(token cashu.Token) (uint64, error) {
return amountAfterSwap, nil
}

// NUT #03: The swap operation is the most important component of the Cashu system. A swap operation consists of multiple inputs (`Proofs`) and outputs (`BlindedMessages`). Mints verify and invalidate the inputs and issue new promises (`BlindSignatures`). These are then used by the wallet to generate new Proofs (see [NUT-00][00]).
func (w *TollWallet) Send(amount uint64, mintUrl string, includeFees bool) (cashu.Token, error) {
log.Printf("TollWallet.Send: attempting to send %d sats from mint %s (includeFees=%t)", amount, mintUrl, includeFees)

Expand Down Expand Up @@ -269,18 +271,21 @@ func (w *TollWallet) SendWithOverpayment(amount uint64, mintUrl string, maxOverp
return tokenString, nil
}

// NUT #04: To request a mint quote, the wallet of `Alice` makes a `POST /v1/mint/quote/{method}` request where `method` is the payment method requested (e.g., `bolt11`, `bolt12`, etc.). `method` **MUST** match `[a-z0-9_-]+`.
func (w *TollWallet) RequestMintQuote(amount uint64, mintURL string) (*nut04.PostMintQuoteBolt11Response, error) {
w.ensureMintRegistered(mintURL)
return w.wallet.RequestMint(amount, mintURL)
}

// NUT #04: To check the current accounting data of a mint quote, the wallet makes a `GET /v1/mint/quote/{method}/{quote_id}`.
func (w *TollWallet) GetMintQuoteState(quoteID string) (*nut04.PostMintQuoteBolt11Response, error) {
if w.wallet == nil {
return nil, ErrWalletNotInitialized
}
return w.wallet.MintQuoteState(quoteID)
}

// NUT #04: After requesting a mint quote and paying the request, the wallet proceeds with minting new tokens by calling the `POST /v1/mint/{method}` endpoint.
func (w *TollWallet) MintQuoteTokens(quoteID string) (uint64, error) {
return w.wallet.MintTokens(quoteID)
}
Expand Down Expand Up @@ -367,6 +372,8 @@ func (w *TollWallet) GetAllMintBalances() map[string]uint64 {
return w.wallet.GetBalanceByMints()
}

// NUT #05: To request a melt quote, the wallet of `Alice` makes a `POST /v1/melt/quote/{method}` request where `method` is the payment method requested (e.g., `bolt11`, `bolt12`, etc.). `method` **MUST** match `[a-z0-9_-]+`.

// MeltToLightning melts a token to a lightning invoice using LNURL
// It attempts to melt for the target amount, reducing by 5% each time if fees are too high
func (w *TollWallet) MeltToLightning(mintUrl string, targetAmount uint64, maxCost uint64, lnurl string) error {
Expand Down