-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
361 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package payment | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/nervosnetwork/ckb-sdk-go/address" | ||
"github.com/nervosnetwork/ckb-sdk-go/collector" | ||
"github.com/nervosnetwork/ckb-sdk-go/crypto" | ||
"github.com/nervosnetwork/ckb-sdk-go/indexer" | ||
"github.com/nervosnetwork/ckb-sdk-go/rpc" | ||
"github.com/nervosnetwork/ckb-sdk-go/transaction" | ||
"github.com/nervosnetwork/ckb-sdk-go/transaction/builder" | ||
"github.com/nervosnetwork/ckb-sdk-go/types" | ||
"github.com/nervosnetwork/ckb-sdk-go/utils" | ||
"github.com/pkg/errors" | ||
"math/big" | ||
) | ||
|
||
type Sudt struct { | ||
Sender *types.Script | ||
Receiver *types.Script | ||
UUID string | ||
Amount *big.Int | ||
FeeRate uint64 | ||
|
||
tx *types.Transaction | ||
systemScripts *utils.SystemScripts | ||
} | ||
|
||
// NewSudt returns a new NewSudt object | ||
func NewSudt(senderAddr, receiverAddr, uuid, amount string, feeRate uint64, systemScripts *utils.SystemScripts) (*Sudt, error) { | ||
parsedSenderAddr, err := address.Parse(senderAddr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
parsedReceiverAddr, err := address.Parse(receiverAddr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
n, b := big.NewInt(0).SetString(amount, 10) | ||
if !b { | ||
return nil, errors.WithMessage(err, "invalid amount") | ||
} | ||
|
||
return &Sudt{ | ||
Sender: parsedSenderAddr.Script, | ||
Receiver: parsedReceiverAddr.Script, | ||
UUID: uuid, | ||
Amount: n, | ||
FeeRate: feeRate, | ||
systemScripts: systemScripts, | ||
}, nil | ||
} | ||
|
||
// GenerateTransferSudtUnsignedTx generate an unsigned transaction for transfer sudt | ||
func (s *Sudt) GenerateTransferSudtUnsignedTx(client rpc.Client) (*types.Transaction, error) { | ||
// udt type script | ||
udtType := &types.Script{ | ||
CodeHash: s.systemScripts.SUDTCell.CellHash, | ||
HashType: s.systemScripts.SUDTCell.HashType, | ||
Args: common.FromHex(s.UUID), | ||
} | ||
searchKey := &indexer.SearchKey{ | ||
Script: s.Sender, | ||
ScriptType: indexer.ScriptTypeLock, | ||
} | ||
// sudt Iterator | ||
sudtCollector := collector.NewLiveCellCollector(client, searchKey, indexer.SearchOrderAsc, indexer.SearchLimit, "") | ||
sudtCollector.TypeScript = udtType | ||
sudtIterator, err := sudtCollector.Iterator() | ||
if err != nil { | ||
return nil, errors.Errorf("collect sudt cells error: %v", err) | ||
} | ||
// ckb Iterator | ||
ckbCollector := collector.NewLiveCellCollector(client, searchKey, indexer.SearchOrderAsc, indexer.SearchLimit, "") | ||
ckbCollector.EmptyData = true | ||
ckbIterator, err := ckbCollector.Iterator() | ||
if err != nil { | ||
return nil, errors.Errorf("collect sudt cells error: %v", err) | ||
} | ||
director := builder.Director{} | ||
txBuilder := &builder.SudtTransferUnsignedTxBuilder{ | ||
Sender: s.Sender, | ||
Receiver: s.Receiver, | ||
FeeRate: s.FeeRate, | ||
CkbIterator: ckbIterator, | ||
SUDTIterator: sudtIterator, | ||
SystemScripts: s.systemScripts, | ||
TransferAmount: s.Amount, | ||
UUID: s.UUID, | ||
} | ||
|
||
director.SetBuilder(txBuilder) | ||
tx, _, err := director.Generate() | ||
s.tx = tx | ||
|
||
return tx, err | ||
} | ||
|
||
// SignTx sign an unsigned sudt transfer transaction and return an signed transaction | ||
func (s *Sudt) SignTx(key crypto.Key) (*types.Transaction, error) { | ||
err := transaction.SingleSegmentSignTransaction(s.tx, 0, len(s.tx.Witnesses), transaction.EmptyWitnessArg, key) | ||
if err != nil { | ||
return nil, fmt.Errorf("sign transaction error: %v", err) | ||
} | ||
return s.tx, nil | ||
} | ||
|
||
// Send can send a tx to tx pool | ||
func (s *Sudt) Send(client rpc.Client) (*types.Hash, error) { | ||
return client.SendTransaction(context.Background(), s.tx) | ||
} |
248 changes: 248 additions & 0 deletions
248
transaction/builder/sudt_transfer_unsigned_tx_builder.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
package builder | ||
|
||
import ( | ||
"bytes" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/nervosnetwork/ckb-sdk-go/collector" | ||
"github.com/nervosnetwork/ckb-sdk-go/transaction" | ||
"github.com/nervosnetwork/ckb-sdk-go/types" | ||
"github.com/nervosnetwork/ckb-sdk-go/utils" | ||
"github.com/pkg/errors" | ||
"math" | ||
"math/big" | ||
) | ||
|
||
var _ UnsignedTxBuilder = (*SudtTransferUnsignedTxBuilder)(nil) | ||
|
||
type SudtTransferUnsignedTxBuilder struct { | ||
Sender *types.Script | ||
Receiver *types.Script | ||
FeeRate uint64 | ||
CkbIterator collector.CellCollectionIterator | ||
SUDTIterator collector.CellCollectionIterator | ||
SystemScripts *utils.SystemScripts | ||
TransferAmount *big.Int | ||
UUID string | ||
|
||
tx *types.Transaction | ||
result *collector.LiveCellCollectResult | ||
ckbChangeOutputIndex *collector.ChangeOutputIndex | ||
sUDTChangeOutputIndex *collector.ChangeOutputIndex | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) NewTransaction() { | ||
s.tx = &types.Transaction{} | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) BuildVersion() { | ||
s.tx.Version = 0 | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) BuildHeaderDeps() { | ||
s.tx.HeaderDeps = []types.Hash{} | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) BuildCellDeps() { | ||
s.tx.CellDeps = []*types.CellDep{ | ||
{ | ||
OutPoint: s.SystemScripts.SecpSingleSigCell.OutPoint, | ||
DepType: types.DepTypeDepGroup, | ||
}, | ||
{ | ||
OutPoint: s.SystemScripts.SUDTCell.OutPoint, | ||
DepType: s.SystemScripts.SUDTCell.DepType, | ||
}, | ||
} | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) BuildOutputsAndOutputsData() error { | ||
udtType := &types.Script{ | ||
CodeHash: s.SystemScripts.SUDTCell.CellHash, | ||
HashType: s.SystemScripts.SUDTCell.HashType, | ||
Args: common.FromHex(s.UUID), | ||
} | ||
// set receiver sudt output | ||
s.tx.Outputs = append(s.tx.Outputs, &types.CellOutput{ | ||
Capacity: udtCellCapacity, | ||
Lock: &types.Script{ | ||
CodeHash: s.Receiver.CodeHash, | ||
HashType: s.Receiver.HashType, | ||
Args: s.Receiver.Args, | ||
}, | ||
Type: udtType, | ||
}) | ||
s.tx.OutputsData = append(s.tx.OutputsData, utils.GenerateSudtAmount(s.TransferAmount)) | ||
|
||
// set ckb change output | ||
s.tx.Outputs = append(s.tx.Outputs, &types.CellOutput{ | ||
Capacity: 0, | ||
Lock: s.Sender, | ||
}) | ||
s.tx.OutputsData = append(s.tx.OutputsData, []byte{}) | ||
// set ckb change output index | ||
s.ckbChangeOutputIndex = &collector.ChangeOutputIndex{Value: 1} | ||
|
||
// set sudt change output | ||
s.tx.Outputs = append(s.tx.Outputs, &types.CellOutput{ | ||
Capacity: udtCellCapacity, | ||
Lock: s.Sender, | ||
Type: udtType, | ||
}) | ||
s.tx.OutputsData = append(s.tx.OutputsData, sudtDataPlaceHolder) | ||
// set sudt change output index | ||
s.sUDTChangeOutputIndex = &collector.ChangeOutputIndex{Value: 2} | ||
|
||
return nil | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) BuildInputsAndWitnesses() error { | ||
if s.TransferAmount == nil { | ||
return errors.New("transfer amount is required") | ||
} | ||
// collect sudt cells first | ||
err := s.collectSUDTCells() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// then collect ckb cells | ||
err = s.collectCkbCells() | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) UpdateChangeOutput() error { | ||
// update sudt change output first | ||
totalAmount := s.result.Options["totalAmount"].(*big.Int) | ||
if totalAmount.Cmp(s.TransferAmount) > 0 && bytes.Compare(s.tx.OutputsData[s.sUDTChangeOutputIndex.Value], sudtDataPlaceHolder) == 0 { | ||
s.tx.OutputsData[s.sUDTChangeOutputIndex.Value] = utils.GenerateSudtAmount(big.NewInt(0).Sub(totalAmount, s.TransferAmount)) | ||
} | ||
if totalAmount.Cmp(s.TransferAmount) == 0 { | ||
s.tx.Outputs = utils.RemoveCellOutput(s.tx.Outputs, s.sUDTChangeOutputIndex.Value) | ||
s.tx.OutputsData = utils.RemoveCellOutputData(s.tx.OutputsData, s.sUDTChangeOutputIndex.Value) | ||
} | ||
|
||
// then update ckb change output | ||
fee, err := transaction.CalculateTransactionFee(s.tx, s.FeeRate) | ||
if err != nil { | ||
return err | ||
} | ||
changeCapacity := s.result.Capacity - s.tx.OutputsCapacity() - fee | ||
s.tx.Outputs[s.ckbChangeOutputIndex.Value].Capacity = changeCapacity | ||
|
||
return nil | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) GetResult() (*types.Transaction, [][]int) { | ||
return s.tx, nil | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) collectCkbCells() error { | ||
for s.CkbIterator.HasNext() { | ||
liveCell, err := s.CkbIterator.CurrentItem() | ||
if err != nil { | ||
return err | ||
} | ||
s.result.Capacity += liveCell.Output.Capacity | ||
s.result.LiveCells = append(s.result.LiveCells, liveCell) | ||
input := &types.CellInput{ | ||
Since: 0, | ||
PreviousOutput: &types.OutPoint{ | ||
TxHash: liveCell.OutPoint.TxHash, | ||
Index: liveCell.OutPoint.Index, | ||
}, | ||
} | ||
s.tx.Inputs = append(s.tx.Inputs, input) | ||
s.tx.Witnesses = append(s.tx.Witnesses, []byte{}) | ||
ok, err := s.isCkbEnough() | ||
if err != nil { | ||
return err | ||
} | ||
if ok { | ||
return nil | ||
} | ||
err = s.CkbIterator.Next() | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return errors.New("insufficient ckb balance") | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) collectSUDTCells() error { | ||
s.result = &collector.LiveCellCollectResult{} | ||
for s.SUDTIterator.HasNext() { | ||
liveCell, err := s.SUDTIterator.CurrentItem() | ||
if err != nil { | ||
return err | ||
} | ||
s.result.Capacity += liveCell.Output.Capacity | ||
s.result.LiveCells = append(s.result.LiveCells, liveCell) | ||
// init totalAmount | ||
if _, ok := s.result.Options["totalAmount"]; !ok { | ||
s.result.Options = make(map[string]interface{}) | ||
s.result.Options["totalAmount"] = big.NewInt(0) | ||
} | ||
amount, err := utils.ParseSudtAmount(liveCell.OutputData) | ||
if err != nil { | ||
return errors.WithMessage(err, "sudt amount parse error") | ||
} | ||
totalAmount := s.result.Options["totalAmount"].(*big.Int) | ||
s.result.Options["totalAmount"] = big.NewInt(0).Add(totalAmount, amount) | ||
input := &types.CellInput{ | ||
Since: 0, | ||
PreviousOutput: &types.OutPoint{ | ||
TxHash: liveCell.OutPoint.TxHash, | ||
Index: liveCell.OutPoint.Index, | ||
}, | ||
} | ||
s.tx.Inputs = append(s.tx.Inputs, input) | ||
s.tx.Witnesses = append(s.tx.Witnesses, []byte{}) | ||
if len(s.tx.Witnesses[0]) == 0 { | ||
s.tx.Witnesses[0] = transaction.EmptyWitnessArgPlaceholder | ||
} | ||
// stop collect | ||
if s.isSUDTEnough() { | ||
return nil | ||
} | ||
err = s.SUDTIterator.Next() | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return errors.New("insufficient sudt balance") | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) isSUDTEnough() bool { | ||
totalAmount := s.result.Options["totalAmount"].(*big.Int) | ||
if totalAmount.Cmp(s.TransferAmount) >= 0 { | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
func (s *SudtTransferUnsignedTxBuilder) isCkbEnough() (bool, error) { | ||
inputsCapacity := big.NewInt(0).SetUint64(s.result.Capacity) | ||
outputsCapacity := big.NewInt(0).SetUint64(s.tx.OutputsCapacity()) | ||
changeCapacity := big.NewInt(0).Sub(inputsCapacity, outputsCapacity) | ||
if changeCapacity.Cmp(big.NewInt(0)) > 0 { | ||
fee, err := transaction.CalculateTransactionFee(s.tx, s.FeeRate) | ||
if err != nil { | ||
return false, err | ||
} | ||
changeCapacity = big.NewInt(0).Sub(changeCapacity, big.NewInt(0).SetUint64(fee)) | ||
changeOutput := s.tx.Outputs[s.ckbChangeOutputIndex.Value] | ||
changeOutputData := s.tx.OutputsData[s.ckbChangeOutputIndex.Value] | ||
|
||
changeOutputCapacity := big.NewInt(0).SetUint64(changeOutput.OccupiedCapacity(changeOutputData) * uint64(math.Pow10(8))) | ||
if changeCapacity.Cmp(changeOutputCapacity) >= 0 { | ||
return true, nil | ||
} else { | ||
return false, nil | ||
} | ||
} else { | ||
return false, nil | ||
} | ||
} |