Skip to content

Commit a61170d

Browse files
author
David Vorick
authored
Merge pull request NebulousLabs#1889 from NebulousLabs/send-to-many
use types.SiacoinOutput for /wallet/siacoins param
2 parents 9eede0b + bcf614d commit a61170d

File tree

6 files changed

+74
-69
lines changed

6 files changed

+74
-69
lines changed

api/wallet.go

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package api
22

33
import (
4+
"encoding/json"
45
"net/http"
56
"path/filepath"
67
"strconv"
@@ -367,42 +368,44 @@ func (api *API) walletSeedsHandler(w http.ResponseWriter, req *http.Request, _ h
367368

368369
// walletSiacoinsHandler handles API calls to /wallet/siacoins.
369370
func (api *API) walletSiacoinsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
370-
amountStrs := strings.Split(req.FormValue("amount"), ",")
371-
destStrs := strings.Split(req.FormValue("destination"), ",")
372-
amounts := make([]types.Currency, len(amountStrs))
373-
dests := make([]types.UnlockHash, len(destStrs))
374-
for i, amountStr := range amountStrs {
375-
var ok bool
376-
amounts[i], ok = scanAmount(amountStr)
377-
if !ok {
378-
WriteError(w, Error{"could not read amount from POST call to /wallet/siacoins"}, http.StatusBadRequest)
371+
var txns []types.Transaction
372+
if req.FormValue("outputs") != "" {
373+
// multiple amounts + destinations
374+
if req.FormValue("amount") != "" || req.FormValue("destination") != "" {
375+
WriteError(w, Error{"cannot supply both 'outputs' and single amount+destination pair"}, http.StatusInternalServerError)
379376
return
380377
}
381-
}
382-
var err error
383-
for i, destStr := range destStrs {
384-
dests[i], err = scanAddress(destStr)
378+
379+
var outputs []types.SiacoinOutput
380+
err := json.Unmarshal([]byte(req.FormValue("outputs")), &outputs)
385381
if err != nil {
386-
WriteError(w, Error{"could not read address from POST call to /wallet/siacoins"}, http.StatusBadRequest)
382+
WriteError(w, Error{"could not decode outputs: " + err.Error()}, http.StatusInternalServerError)
387383
return
388384
}
389-
}
390-
391-
var txns []types.Transaction
392-
if len(amounts) == 1 && len(dests) == 1 {
393-
// single amount + destination
394-
txns, err = api.wallet.SendSiacoins(amounts[0], dests[0])
385+
txns, err = api.wallet.SendSiacoinsMulti(outputs)
395386
if err != nil {
396387
WriteError(w, Error{"error after call to /wallet/siacoins: " + err.Error()}, http.StatusInternalServerError)
397388
return
398389
}
399390
} else {
400-
// multiple amounts + destinations
401-
txns, err = api.wallet.SendSiacoinsMulti(amounts, dests)
391+
// single amount + destination
392+
amount, ok := scanAmount(req.FormValue("amount"))
393+
if !ok {
394+
WriteError(w, Error{"could not read amount from POST call to /wallet/siacoins"}, http.StatusBadRequest)
395+
return
396+
}
397+
dest, err := scanAddress(req.FormValue("destination"))
398+
if err != nil {
399+
WriteError(w, Error{"could not read address from POST call to /wallet/siacoins"}, http.StatusBadRequest)
400+
return
401+
}
402+
403+
txns, err = api.wallet.SendSiacoins(amount, dest)
402404
if err != nil {
403405
WriteError(w, Error{"error after call to /wallet/siacoins: " + err.Error()}, http.StatusInternalServerError)
404406
return
405407
}
408+
406409
}
407410

408411
var txids []types.TransactionID

api/wallet_test.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package api
22

33
import (
4+
"encoding/json"
45
"errors"
56
"fmt"
67
"net/url"
78
"os"
89
"path/filepath"
9-
"strings"
1010
"testing"
1111
"time"
1212

@@ -1501,46 +1501,52 @@ func TestWalletSiacoins(t *testing.T) {
15011501
t.Fatal(err)
15021502
}
15031503
sendSiacoinsValues := url.Values{}
1504-
sendSiacoinsValues.Set("amount", sendAmount.String())
1505-
sendSiacoinsValues.Set("destination", wag.Address.String())
1504+
outputsJSON, _ := json.Marshal([]types.SiacoinOutput{{
1505+
UnlockHash: wag.Address,
1506+
Value: sendAmount,
1507+
}})
1508+
sendSiacoinsValues.Set("outputs", string(outputsJSON))
15061509
if err = st.stdPostAPI("/wallet/siacoins", sendSiacoinsValues); err != nil {
15071510
t.Fatal(err)
15081511
}
15091512

15101513
// Send 10KS to 3, 4, 5 in a single send.
1511-
var amounts, dests []string
1514+
var outputs []types.SiacoinOutput
15121515
for _, w := range wallets[2:5] {
15131516
var wag WalletAddressGET
15141517
err = w.getAPI("/wallet/address", &wag)
15151518
if err != nil {
15161519
t.Fatal(err)
15171520
}
1518-
amounts = append(amounts, sendAmount.String())
1519-
dests = append(dests, wag.Address.String())
1521+
outputs = append(outputs, types.SiacoinOutput{
1522+
UnlockHash: wag.Address,
1523+
Value: sendAmount,
1524+
})
15201525
}
1526+
outputsJSON, _ = json.Marshal(outputs)
15211527
sendSiacoinsValues = url.Values{}
1522-
sendSiacoinsValues.Set("amount", strings.Join(amounts, ","))
1523-
sendSiacoinsValues.Set("destination", strings.Join(dests, ","))
1528+
sendSiacoinsValues.Set("outputs", string(outputsJSON))
15241529
if err = st.stdPostAPI("/wallet/siacoins", sendSiacoinsValues); err != nil {
15251530
t.Fatal(err)
15261531
}
15271532

15281533
// Send 10KS to 6 through a joined 250 sends.
1529-
amounts = nil
1530-
dests = nil
1534+
outputs = nil
15311535
smallSend := sendAmount.Div64(250)
15321536
for i := 0; i < 250; i++ {
15331537
var wag WalletAddressGET
15341538
err = st6.getAPI("/wallet/address", &wag)
15351539
if err != nil {
15361540
t.Fatal(err)
15371541
}
1538-
amounts = append(amounts, smallSend.String())
1539-
dests = append(dests, wag.Address.String())
1542+
outputs = append(outputs, types.SiacoinOutput{
1543+
UnlockHash: wag.Address,
1544+
Value: smallSend,
1545+
})
15401546
}
1547+
outputsJSON, _ = json.Marshal(outputs)
15411548
sendSiacoinsValues = url.Values{}
1542-
sendSiacoinsValues.Set("amount", strings.Join(amounts, ","))
1543-
sendSiacoinsValues.Set("destination", strings.Join(dests, ","))
1549+
sendSiacoinsValues.Set("outputs", string(outputsJSON))
15441550
if err = st.stdPostAPI("/wallet/siacoins", sendSiacoinsValues); err != nil {
15451551
t.Fatal(err)
15461552
}

doc/API.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,14 +1177,15 @@ dictionary
11771177

11781178
#### /wallet/siacoins [POST]
11791179

1180-
sends siacoins to a set of addresses. The outputs are arbitrarily selected
1181-
from addresses in the wallet. The number of amounts must match the number of
1182-
destinations.
1180+
sends siacoins to an address or set of addresses. The outputs are arbitrarily
1181+
selected from addresses in the wallet. If 'outputs' is supplied, 'amount' and
1182+
'destination' must be empty.
11831183

11841184
###### Query String Parameters [(with comments)](/doc/api/Wallet.md#query-string-parameters-6)
11851185
```
1186-
amount // list of hastings (comma separated)
1187-
destination // list of addresses (comma separated)
1186+
amount // hastings
1187+
destination // address
1188+
outputs // JSON array of {unlockhash, value} pairs
11881189
```
11891190

11901191
###### JSON Response [(with comments)](/doc/api/Wallet.md#json-response-5)

doc/api/Wallet.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -314,20 +314,24 @@ dictionary
314314

315315
#### /wallet/siacoins [POST]
316316

317-
Function: Send siacoins to a set of addresses. The outputs are arbitrarily
318-
selected from addresses in the wallet. Each address is paired with an amount;
319-
the number of amounts must match the number of addresses. The number of pairs
320-
should not exceed 250; this may result in a transaction too large to fit in
321-
the transaction pool.
317+
Function: Send siacoins to an address or set of addresses. The outputs are
318+
arbitrarily selected from addresses in the wallet. If 'outputs' is supplied,
319+
'amount' and 'destination' must be empty. The number of outputs should not
320+
exceed 400; this may result in a transaction too large to fit in the
321+
transaction pool.
322322

323323
###### Query String Parameters
324324
```
325-
// Comma-separated list of hastings being sent to each address. A hasting is
326-
// the smallest unit in Sia. There are 10^24 hastings in a siacoin.
325+
// Number of hastings being sent. A hasting is the smallest unit in Sia. There
326+
// are 10^24 hastings in a siacoin.
327327
amount // hastings
328328
329-
// Comma-separated list of addresses that are receiving coins.
329+
// Address that is receiving the coins.
330330
destination // address
331+
332+
// JSON array of outputs. The structure of each output is:
333+
// {"unlockhash": "<destination>", "value": "<amount>"}
334+
outputs
331335
```
332336

333337
###### JSON Response

modules/wallet.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,9 +383,8 @@ type (
383383
// are also returned to the caller.
384384
SendSiacoins(amount types.Currency, dest types.UnlockHash) ([]types.Transaction, error)
385385

386-
// SendSiacoinsMulti sends coins to multiple addresses. The number of
387-
// amounts and destinations must match.
388-
SendSiacoinsMulti(amounts []types.Currency, dests []types.UnlockHash) ([]types.Transaction, error)
386+
// SendSiacoinsMulti sends coins to multiple addresses.
387+
SendSiacoinsMulti(outputs []types.SiacoinOutput) ([]types.Transaction, error)
389388

390389
// SendSiafunds is a tool for sending siafunds from the wallet to an
391390
// address. Sending money usually results in multiple transactions. The

modules/wallet/money.go

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package wallet
22

33
import (
4-
"errors"
5-
64
"github.com/NebulousLabs/Sia/build"
75
"github.com/NebulousLabs/Sia/modules"
86
"github.com/NebulousLabs/Sia/types"
@@ -113,10 +111,10 @@ func (w *Wallet) SendSiacoins(amount types.Currency, dest types.UnlockHash) ([]t
113111
return txnSet, nil
114112
}
115113

116-
// SendSiacoinsMulti creates a transaction sending each amount in 'amounts' to
117-
// it's corresponding destination in 'dests'. The transaction is submitted to
118-
// the transaction pool and is also returned.
119-
func (w *Wallet) SendSiacoinsMulti(amounts []types.Currency, dests []types.UnlockHash) ([]types.Transaction, error) {
114+
// SendSiacoinsMulti creates a transaction that includes the specified
115+
// outputs. The transaction is submitted to the transaction pool and is also
116+
// returned.
117+
func (w *Wallet) SendSiacoinsMulti(outputs []types.SiacoinOutput) ([]types.Transaction, error) {
120118
if err := w.tg.Add(); err != nil {
121119
return nil, err
122120
}
@@ -125,35 +123,29 @@ func (w *Wallet) SendSiacoinsMulti(amounts []types.Currency, dests []types.Unloc
125123
w.log.Println("Attempt to send coins has failed - wallet is locked")
126124
return nil, modules.ErrLockedWallet
127125
}
128-
if len(amounts) != len(dests) {
129-
return nil, errors.New("number of amounts and destinations must match")
130-
}
131126

132127
txnBuilder := w.StartTransaction()
133128

134129
// Add estimated transaction fee.
135130
_, tpoolFee := w.tpool.FeeEstimation()
136-
tpoolFee = tpoolFee.Mul64(1000 + 60*uint64(len(dests))) // Estimated transaction size in bytes
131+
tpoolFee = tpoolFee.Mul64(1000 + 60*uint64(len(outputs))) // Estimated transaction size in bytes
137132
txnBuilder.AddMinerFee(tpoolFee)
138133

139134
// Calculate total cost to wallet.
140135
// NOTE: we only want to call FundSiacoins once; that way, it will
141136
// (ideally) fund the entire transaction with a single input, instead of
142137
// many smaller ones.
143138
totalCost := tpoolFee
144-
for _, amount := range amounts {
145-
totalCost = totalCost.Add(amount)
139+
for _, sco := range outputs {
140+
totalCost = totalCost.Add(sco.Value)
146141
}
147142
err := txnBuilder.FundSiacoins(totalCost)
148143
if err != nil {
149144
return nil, build.ExtendErr("unable to fund transaction", err)
150145
}
151146

152-
for i := range dests {
153-
txnBuilder.AddSiacoinOutput(types.SiacoinOutput{
154-
Value: amounts[i],
155-
UnlockHash: dests[i],
156-
})
147+
for _, sco := range outputs {
148+
txnBuilder.AddSiacoinOutput(sco)
157149
}
158150

159151
txnSet, err := txnBuilder.Sign(true)

0 commit comments

Comments
 (0)