Files
sui-go-sdk/sui/write_transaction_api.go

448 lines
15 KiB
Go

// Copyright (c) BlockVision, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package sui
import (
"context"
"encoding/json"
"errors"
"github.com/block-vision/sui-go-sdk/common/httpconn"
"github.com/block-vision/sui-go-sdk/models"
"github.com/tidwall/gjson"
)
type IWriteTransactionAPI interface {
SuiExecuteTransactionBlock(ctx context.Context, req models.SuiExecuteTransactionBlockRequest) (models.SuiTransactionBlockResponse, error)
MoveCall(ctx context.Context, req models.MoveCallRequest) (models.TxnMetaData, error)
MergeCoins(ctx context.Context, req models.MergeCoinsRequest) (models.TxnMetaData, error)
SplitCoin(ctx context.Context, req models.SplitCoinRequest) (models.TxnMetaData, error)
SplitCoinEqual(ctx context.Context, req models.SplitCoinEqualRequest) (models.TxnMetaData, error)
Publish(ctx context.Context, req models.PublishRequest) (models.TxnMetaData, error)
TransferObject(ctx context.Context, req models.TransferObjectRequest) (models.TxnMetaData, error)
TransferSui(ctx context.Context, req models.TransferSuiRequest) (models.TxnMetaData, error)
Pay(ctx context.Context, req models.PayRequest) (models.TxnMetaData, error)
PaySui(ctx context.Context, req models.PaySuiRequest) (models.TxnMetaData, error)
PayAllSui(ctx context.Context, req models.PayAllSuiRequest) (models.TxnMetaData, error)
RequestAddStake(ctx context.Context, req models.AddStakeRequest) (models.TxnMetaData, error)
RequestWithdrawStake(ctx context.Context, req models.WithdrawStakeRequest) (models.TxnMetaData, error)
BatchTransaction(ctx context.Context, req models.BatchTransactionRequest) (models.BatchTransactionResponse, error)
SignAndExecuteTransactionBlock(ctx context.Context, req models.SignAndExecuteTransactionBlockRequest) (models.SuiTransactionBlockResponse, error)
}
type suiWriteTransactionImpl struct {
conn *httpconn.HttpConn
}
// SuiExecuteTransactionBlock implements the method `sui_executeTransactionBlock`, executes a transaction using the transaction data and signature(s).
func (s *suiWriteTransactionImpl) SuiExecuteTransactionBlock(ctx context.Context, req models.SuiExecuteTransactionBlockRequest) (models.SuiTransactionBlockResponse, error) {
var rsp models.SuiTransactionBlockResponse
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "sui_executeTransactionBlock",
Params: []interface{}{
req.TxBytes,
req.Signature,
req.Options,
req.RequestType,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// MoveCall implements the method `unsafe_moveCall`, creates an unsigned transaction to execute a Move call on the network, by calling the specified function in the module of a given package.
func (s *suiWriteTransactionImpl) MoveCall(ctx context.Context, req models.MoveCallRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_moveCall",
Params: []interface{}{
req.Signer,
req.PackageObjectId,
req.Module,
req.Function,
req.TypeArguments,
req.Arguments,
req.Gas,
req.GasBudget,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// MergeCoins implements the method `unsafe_mergeCoins`, creates an unsigned transaction to merge multiple coins into one coin.
func (s *suiWriteTransactionImpl) MergeCoins(ctx context.Context, req models.MergeCoinsRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_mergeCoins",
Params: []interface{}{
req.Signer,
req.PrimaryCoin,
req.CoinToMerge,
req.Gas,
req.GasBudget,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// SplitCoin implements the method `unsafe_splitCoin`, creates an unsigned transaction to split a coin object into multiple coins.
func (s *suiWriteTransactionImpl) SplitCoin(ctx context.Context, req models.SplitCoinRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_splitCoin",
Params: []interface{}{
req.Signer,
req.CoinObjectId,
req.SplitAmounts,
req.Gas,
req.GasBudget,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// SplitCoinEqual implements the method `unsafe_splitCoinEqual`, creates an unsigned transaction to split a coin object into multiple equal-size coins.
func (s *suiWriteTransactionImpl) SplitCoinEqual(ctx context.Context, req models.SplitCoinEqualRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_splitCoinEqual",
Params: []interface{}{
req.Signer,
req.CoinObjectId,
req.SplitCount,
req.Gas,
req.GasBudget,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// Publish implements the method `unsafe_publish`, creates an unsigned transaction to publish a Move package.
func (s *suiWriteTransactionImpl) Publish(ctx context.Context, req models.PublishRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_publish",
Params: []interface{}{
req.Sender,
req.CompiledModules,
req.Dependencies,
req.Gas,
req.GasBudget,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// TransferObject implements the method `unsafe_transferObject`, creates an unsigned transaction to transfer an object from one address to another. The object's type must allow public transfers.
func (s *suiWriteTransactionImpl) TransferObject(ctx context.Context, req models.TransferObjectRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_transferObject",
Params: []interface{}{
req.Signer,
req.ObjectId,
req.Gas,
req.GasBudget,
req.Recipient,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// TransferSui implements the method `unsafe_transferSui`, creates an unsigned transaction to send SUI coin object to a Sui address. The SUI object is also used as the gas object.
func (s *suiWriteTransactionImpl) TransferSui(ctx context.Context, req models.TransferSuiRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_transferSui",
Params: []interface{}{
req.Signer,
req.SuiObjectId,
req.GasBudget,
req.Recipient,
req.Amount,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// Pay implements the method `unsafe_pay`, send `Coin<T>` to a list of addresses, where `T` can be any coin type, following a list of amounts.
// The object specified in the `gas` field will be used to pay the gas fee for the transaction.
// The gas object can not appear in `input_coins`. If the gas object is not specified, the RPC server will auto-select one.
func (s *suiWriteTransactionImpl) Pay(ctx context.Context, req models.PayRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_pay",
Params: []interface{}{
req.Signer,
req.SuiObjectId,
req.Recipient,
req.Amount,
req.Gas,
req.GasBudget,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// PaySui implements the method `unsafe_paySui`, send SUI coins to a list of addresses, following a list of amounts.
// This is for SUI coin only and does not require a separate gas coin object.
// Specifically, what pay_sui does are:
// 1. debit each input_coin to create new coin following the order of amounts and assign it to the corresponding recipient.
// 2. accumulate all residual SUI from input coins left and deposit all SUI to the first input coin, then use the first input coin as the gas coin object.
// 3. the balance of the first input coin after tx is sum(input_coins) - sum(amounts) - actual_gas_cost
// 4. all other input coints other than the first one are deleted.
func (s *suiWriteTransactionImpl) PaySui(ctx context.Context, req models.PaySuiRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_paySui",
Params: []interface{}{
req.Signer,
req.SuiObjectId,
req.Recipient,
req.Amount,
req.GasBudget,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// PayAllSui implements the method `unsafe_payAllSui`, send all SUI coins to one recipient.
// This is for SUI coin only and does not require a separate gas coin object.
// Specifically, what pay_all_sui does are:
// 1. accumulate all SUI from input coins and deposit all SUI to the first input coin.
// 2. transfer the updated first coin to the recipient and also use this first coin as gas coin object.
// 3. the balance of the first input coin after tx is sum(input_coins) - actual_gas_cost.
// 4. all other input coins other than the first are deleted.
func (s *suiWriteTransactionImpl) PayAllSui(ctx context.Context, req models.PayAllSuiRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_payAllSui",
Params: []interface{}{
req.Signer,
req.SuiObjectId,
req.Recipient,
req.GasBudget,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// RequestAddStake implements the method `unsafe_requestAddStake`, add stake to a validator's staking pool using multiple coins and amount.
func (s *suiWriteTransactionImpl) RequestAddStake(ctx context.Context, req models.AddStakeRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_requestAddStake",
Params: []interface{}{
req.Signer,
req.Coins,
req.Amount,
req.Validator,
req.Gas,
req.GasBudget,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// RequestWithdrawStake implements the method `unsafe_requestWithdrawStake`, withdraw stake from a validator's staking pool.
func (s *suiWriteTransactionImpl) RequestWithdrawStake(ctx context.Context, req models.WithdrawStakeRequest) (models.TxnMetaData, error) {
var rsp models.TxnMetaData
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_requestWithdrawStake",
Params: []interface{}{
req.Signer,
req.StakedObjectId,
req.Gas,
req.GasBudget,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// BatchTransaction implements the method `unsafe_batchTransaction`, creates an unsigned batched transaction.
func (s *suiWriteTransactionImpl) BatchTransaction(ctx context.Context, req models.BatchTransactionRequest) (models.BatchTransactionResponse, error) {
var rsp models.BatchTransactionResponse
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "unsafe_batchTransaction",
Params: []interface{}{
req.Signer,
req.RPCTransactionRequestParams,
req.Gas,
req.GasBudget,
req.SuiTransactionBlockBuilderMode,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}
// SignAndExecuteTransactionBlock sign a transaction block and submit to the Fullnode for execution.
func (s *suiWriteTransactionImpl) SignAndExecuteTransactionBlock(ctx context.Context, req models.SignAndExecuteTransactionBlockRequest) (models.SuiTransactionBlockResponse, error) {
var rsp models.SuiTransactionBlockResponse
signedTxn := req.TxnMetaData.SignSerializedSigWith(req.PriKey)
respBytes, err := s.conn.Request(ctx, httpconn.Operation{
Method: "sui_executeTransactionBlock",
Params: []interface{}{
signedTxn.TxBytes,
[]string{signedTxn.Signature},
req.Options,
req.RequestType,
},
})
if err != nil {
return rsp, err
}
if gjson.ParseBytes(respBytes).Get("error").Exists() {
return rsp, errors.New(gjson.ParseBytes(respBytes).Get("error").String())
}
err = json.Unmarshal([]byte(gjson.ParseBytes(respBytes).Get("result").String()), &rsp)
if err != nil {
return rsp, err
}
return rsp, nil
}