From 36d05af886052c3efff742e6e8ed5160373136fa Mon Sep 17 00:00:00 2001 From: wuuuk Date: Fri, 17 Jan 2025 16:31:50 +0800 Subject: [PATCH 1/5] update dry run transaction block input --- gmsui/client/client.go | 2 +- gmsui/types/params.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gmsui/client/client.go b/gmsui/client/client.go index 33fd0f0..c9dbf8f 100644 --- a/gmsui/client/client.go +++ b/gmsui/client/client.go @@ -609,7 +609,7 @@ func (client *SuiClient) DryRunTransactionBlock(input types.DryRunTransactionBlo return response, client.request( SuiTransportRequestOptions{ Method: "sui_dryRunTransactionBlock", - Params: []any{input.TransactionBlock}, + Params: []any{b64.ToBase64(input.TransactionBlock)}, }, &response, ) diff --git a/gmsui/types/params.go b/gmsui/types/params.go index 5125fb8..969b709 100644 --- a/gmsui/types/params.go +++ b/gmsui/types/params.go @@ -162,7 +162,7 @@ type ResolveNameServiceAddressParams struct { } type DryRunTransactionBlockParams struct { - TransactionBlock interface{} `json:"transactionBlock"` + TransactionBlock []byte `json:"transactionBlock"` } type DevInspectTransactionBlockParams struct { From 11ecc3a9bb58a20475b4def37a70c132b70cb760 Mon Sep 17 00:00:00 2001 From: wuuuk Date: Fri, 17 Jan 2025 16:34:37 +0800 Subject: [PATCH 2/5] update utils --- gmsui/utils.go | 17 ----------------- gmsui/utils/constants.go | 16 ++++++++++++++++ gmsui/utils/unsigned_integer.go | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 gmsui/utils/constants.go create mode 100644 gmsui/utils/unsigned_integer.go diff --git a/gmsui/utils.go b/gmsui/utils.go index 68c2b22..953af93 100644 --- a/gmsui/utils.go +++ b/gmsui/utils.go @@ -69,20 +69,3 @@ func ParseDevInspectReturnValue(v [2]interface{}) ([]byte, error) { } return bs, nil } - -func UnsignedIntegerToUint64(v interface{}) uint64 { - switch v := v.(type) { - case uint64: - return v - case uint32: - return uint64(v) - case uint16: - return uint64(v) - case uint8: - return uint64(v) - case uint: - return uint64(v) - default: - return 0 - } -} diff --git a/gmsui/utils/constants.go b/gmsui/utils/constants.go new file mode 100644 index 0000000..d048613 --- /dev/null +++ b/gmsui/utils/constants.go @@ -0,0 +1,16 @@ +package utils + +import "fmt" + +var ( + SUI_DECIMALS = 9 + MIST_PER_SUI = 1000000000 + + MOVE_STDLIB_ADDRESS = NormalizeSuiObjectId("0x1") + SUI_FRAMEWORK_ADDRESS = NormalizeSuiObjectId("0x2") + SUI_SYSTEM_ADDRESS = NormalizeSuiObjectId("0x3") + SUI_CLOCK_OBJECT_ID = NormalizeSuiObjectId("0x6") + SUI_SYSTEM_MODULE_NAME = "sui_system" + SUI_TYPE_ARG = fmt.Sprintf("%s::sui::SUI", SUI_FRAMEWORK_ADDRESS) + SUI_SYSTEM_STATE_OBJECT_ID = NormalizeSuiObjectId("0x5") +) diff --git a/gmsui/utils/unsigned_integer.go b/gmsui/utils/unsigned_integer.go new file mode 100644 index 0000000..cb8b01b --- /dev/null +++ b/gmsui/utils/unsigned_integer.go @@ -0,0 +1,18 @@ +package utils + +func UnsignedIntegerToUint64(v any) uint64 { + switch v := v.(type) { + case uint64: + return v + case uint32: + return uint64(v) + case uint16: + return uint64(v) + case uint8: + return uint64(v) + case uint: + return uint64(v) + default: + return 0 + } +} From 8125b8664c72e319305022912b741817935b745c Mon Sep 17 00:00:00 2001 From: wuuuk Date: Sat, 18 Jan 2025 16:15:43 +0800 Subject: [PATCH 3/5] refactor transaction --- gmsui/transaction.go | 509 ------------------------------ gmsui/transactions/internal.go | 435 +++++++++++++++++++++++++ gmsui/transactions/object_ref.go | 36 +++ gmsui/transactions/parameter.go | 123 ++++++++ gmsui/transactions/transaction.go | 291 +++++++++++++++++ gmsui/utils/constants.go | 21 +- gmsui/utils/slice2map.go | 9 + 7 files changed, 905 insertions(+), 519 deletions(-) delete mode 100644 gmsui/transaction.go create mode 100644 gmsui/transactions/internal.go create mode 100644 gmsui/transactions/object_ref.go create mode 100644 gmsui/transactions/parameter.go create mode 100644 gmsui/transactions/transaction.go create mode 100644 gmsui/utils/slice2map.go diff --git a/gmsui/transaction.go b/gmsui/transaction.go deleted file mode 100644 index 5716ca2..0000000 --- a/gmsui/transaction.go +++ /dev/null @@ -1,509 +0,0 @@ -package gmsui - -import ( - "context" - "fmt" - "strconv" - "strings" - - gm "github.com/W3Tools/go-modules" - "github.com/W3Tools/go-modules/gmsui/client" - "github.com/W3Tools/go-modules/gmsui/types" - "github.com/W3Tools/go-modules/gmsui/utils" - "github.com/W3Tools/go-sui-sdk/v2/lib" - "github.com/W3Tools/go-sui-sdk/v2/move_types" - "github.com/W3Tools/go-sui-sdk/v2/sui_types" - "github.com/fardream/go-bcs/bcs" -) - -type Transaction struct { - client *client.SuiClient - builder *sui_types.ProgrammableTransactionBuilder - ctx context.Context -} - -func NewTransaction(client *client.SuiClient) *Transaction { - return &Transaction{ - client: client, - builder: sui_types.NewProgrammableTransactionBuilder(), - ctx: client.Context(), - } -} - -type TransactionInputGasCoin struct { - GasCoin bool `json:"gasCoin"` -} - -func (txb *Transaction) Gas() *TransactionInputGasCoin { - return &TransactionInputGasCoin{GasCoin: true} -} - -func (txb *Transaction) SplitCoins(coin interface{}, amounts []interface{}) (returnArguments []*sui_types.Argument, err error) { - if len(amounts) == 0 { - return nil, fmt.Errorf("got empty amounts") - } - - var inputCoin sui_types.Argument - switch coin := coin.(type) { - case *TransactionInputGasCoin: - inputCoin = sui_types.Argument{GasCoin: &lib.EmptyEnum{}} - case sui_types.Argument: - inputCoin = coin - case string: - result, err := txb.client.GetObject(types.GetObjectParams{ID: utils.NormalizeSuiAddress(coin)}) - if err != nil { - return nil, fmt.Errorf("failed to get object, err: %v", err) - } - - objectId, err := sui_types.NewObjectIdFromHex(result.Data.ObjectId) - if err != nil { - return nil, fmt.Errorf("failed to create object id, err: %v", err) - } - version, err := strconv.ParseUint(result.Data.Version, 10, 64) - if err != nil { - return nil, fmt.Errorf("failed to parse version, err: %v", err) - } - digest, err := sui_types.NewDigest(result.Data.Digest) - if err != nil { - return nil, fmt.Errorf("failed to create digest, err: %v", err) - } - - arg := sui_types.ObjectArg{ - ImmOrOwnedObject: &sui_types.ObjectRef{ - ObjectId: *objectId, - Version: version, - Digest: *digest, - }, - } - inputCoin, err = txb.builder.Obj(arg) - if err != nil { - return nil, fmt.Errorf("failed to create pure argument, err: %v", err) - } - default: - return nil, fmt.Errorf("input coin should one of address(string), sui_types.Argument or *TransactionInputGasCoin, got %T", coin) - } - - amountArguments := make([]sui_types.Argument, len(amounts)) - for i, amount := range amounts { - switch amount := amount.(type) { - case uint, uint8, uint16, uint32, uint64: - amountArguments[i], err = txb.builder.Pure(UnsignedIntegerToUint64(amount)) - if err != nil { - return nil, fmt.Errorf("failed to create pure argument, err: %v", err) - } - case sui_types.Argument: - amountArguments[i] = amount - default: - return nil, fmt.Errorf("input amount should be uint or sui_types.Argument, got %T", amount) - } - } - - txb.builder.Command( - sui_types.Command{ - SplitCoins: &struct { - Argument sui_types.Argument - Arguments []sui_types.Argument - }{ - Argument: inputCoin, - Arguments: amountArguments, - }, - }, - ) - - return txb.createTransactionResult(len(amounts)), nil -} - -func (txb *Transaction) NewMoveCall(target string, args []interface{}, typeArgs []string) (returnArguments []*sui_types.Argument, err error) { - entry := strings.Split(target, "::") - if len(entry) != 3 { - return nil, fmt.Errorf("invalid target [%s]", target) - } - var pkg, mod, fn = utils.NormalizeSuiObjectId(entry[0]), entry[1], entry[2] - - arguments, returnsCount, err := txb.ParseFunctionArguments(pkg, mod, fn, args) - if err != nil { - return nil, fmt.Errorf("failed to parse function arguments, err: %v", err) - } - - typeArguments, err := ParseFunctionTypeArguments(typeArgs) - if err != nil { - return nil, fmt.Errorf("failed to parse function type arguments, err: %v", err) - } - - packageId, err := sui_types.NewAddressFromHex(pkg) - if err != nil { - return nil, fmt.Errorf("invalid package address [%v]", err) - } - - txb.builder.Command( - sui_types.Command{ - MoveCall: &sui_types.ProgrammableMoveCall{ - Package: *packageId, - Module: move_types.Identifier(mod), - Function: move_types.Identifier(fn), - Arguments: arguments, - TypeArguments: typeArguments, - }, - }, - ) - - return txb.createTransactionResult(returnsCount), nil -} - -func (txb *Transaction) createTransactionResult(count int) []*sui_types.Argument { - nestedResult1 := uint16(len(txb.builder.Commands) - 1) - returnArguments := make([]*sui_types.Argument, count) - for i := 0; i < count; i++ { - returnArguments[i] = &sui_types.Argument{ - NestedResult: &struct { - Result1 uint16 - Result2 uint16 - }{ - Result1: nestedResult1, - Result2: uint16(i), - }, - } - } - - return returnArguments -} - -func (txb *Transaction) ParseFunctionArguments(pkg, mod, fn string, args []interface{}) (arguments []sui_types.Argument, returnsCount int, err error) { - normalized, err := txb.client.GetNormalizedMoveFunction(types.GetNormalizedMoveFunctionParams{Package: pkg, Module: mod, Function: fn}) - if err != nil { - return nil, 0, fmt.Errorf("failed to get normalized move function, err: %v", err) - } - - hasTxContext := false - if len(normalized.Parameters) > 0 && isTxContext(normalized.Parameters[len(normalized.Parameters)-1].SuiMoveNormalizedType) { - hasTxContext = true - } - - if hasTxContext { - normalized.Parameters = normalized.Parameters[:len(args)] - } - - if len(args) != len(normalized.Parameters) { - return nil, 0, fmt.Errorf("incorrect number of arguments") - } - - type argumentType struct { - Pure any - Object *sui_types.ObjectArg - typeArgument *sui_types.Argument - } - var ( - inputArguments = make([]*argumentType, len(normalized.Parameters)) - argumentToResolve = make(map[int]struct { - Mutable bool - ObjectId string - }) - ) - - for idx, parameter := range normalized.Parameters { - var ( - inputarg = args[idx] - ) - - byvalue, ok := inputarg.(*sui_types.Argument) - if ok { - inputArguments[idx] = &argumentType{typeArgument: byvalue} - continue - } - - _, ok = parameter.SuiMoveNormalizedType.(types.SuiMoveNormalizedType_Vector) - if ok { - inputArguments[idx] = &argumentType{Pure: inputarg} - continue - } - - pureType, ok := parameter.SuiMoveNormalizedType.(types.SuiMoveNormalizedType_String) - if ok { - var purevalue any - switch pureType { - case "Bool", "U8", "U16", "U32", "U64", "U128", "U256": - purevalue = inputarg - case "Address": - var address *move_types.AccountAddress - address, err = sui_types.NewAddressFromHex(utils.NormalizeSuiAddress(inputarg.(string))) - if err != nil { - return nil, 0, err - } - purevalue = address - default: - err = fmt.Errorf("invalid pure type %v", pureType) - return - } - - inputArguments[idx] = &argumentType{Pure: purevalue} - continue - } - - var resolve struct { - Mutable bool - ObjectId string - } - - resolve.ObjectId, ok = inputarg.(string) - if !ok { - return nil, 0, fmt.Errorf("invalid object [%s]", inputarg) - } - switch parameter.SuiMoveNormalizedType.(type) { - case types.SuiMoveNormalizedType_MutableReference: - resolve.Mutable = true - } - argumentToResolve[idx] = resolve - } - - var ids []string - for _, resolve := range argumentToResolve { - ids = append(ids, resolve.ObjectId) - } - - if len(ids) > 0 { - var objects []*types.SuiObjectResponse - objects, err = txb.client.MultiGetObjects(types.MultiGetObjectsParams{IDs: ids, Options: &types.SuiObjectDataOptions{ShowOwner: true}}) - if err != nil { - return - } - - for idx, resolveObject := range argumentToResolve { - object := gm.FilterOne(objects, func(v *types.SuiObjectResponse) bool { - return v.Data.ObjectId == utils.NormalizeSuiObjectId(resolveObject.ObjectId) - }) - if object == nil { - err = fmt.Errorf("object not found") - return - } - - var ( - objecrArgument sui_types.ObjectArg - objectId *move_types.AccountAddress - ) - - objectId, err = sui_types.NewObjectIdFromHex(object.Data.ObjectId) - if err != nil { - return - } - - switch t := object.Data.Owner.ObjectOwner.(type) { - case types.ObjectOwner_Shared: - objecrArgument.SharedObject = &struct { - Id move_types.AccountAddress - InitialSharedVersion uint64 - Mutable bool - }{ - Id: *objectId, - InitialSharedVersion: t.Shared.InitialSharedVersion, - Mutable: resolveObject.Mutable, - } - default: - version, err := strconv.ParseUint(object.Data.Version, 10, 64) - if err != nil { - return nil, 0, err - } - - digest, err := sui_types.NewDigest(object.Data.Digest) - if err != nil { - return nil, 0, err - } - - objecrArgument.ImmOrOwnedObject = &sui_types.ObjectRef{ - ObjectId: *objectId, - Version: version, - Digest: *digest, - } - } - inputArguments[idx] = &argumentType{Object: &objecrArgument} - } - } - - arguments, _ = gm.Map(inputArguments, func(v *argumentType) (sui_types.Argument, error) { - if v.Pure != nil { - return txb.builder.Pure(v.Pure) - } - - if v.Object != nil { - return txb.builder.Obj(*v.Object) - } - - if v.typeArgument != nil { - return *v.typeArgument, nil - } - - return sui_types.Argument{}, fmt.Errorf("invalid argument") - }) - return arguments, len(normalized.Return), nil -} - -func ParseFunctionTypeArguments(typeArgs []string) (typeArguments []move_types.TypeTag, err error) { - typeArguments = []move_types.TypeTag{} - - for _, arg := range typeArgs { - entry := strings.Split(arg, "::") - if len(entry) != 3 { - return nil, fmt.Errorf("type arguments parsing failed, invalid target [%s]", arg) - } - - typeAddress, err := sui_types.NewObjectIdFromHex(entry[0]) - if err != nil { - return nil, fmt.Errorf("invalid package address [%v]", err) - } - - typeTag := move_types.TypeTag{ - Struct: &move_types.StructTag{ - Address: *typeAddress, - Module: move_types.Identifier(entry[1]), - Name: move_types.Identifier(entry[2]), - }, - } - typeArguments = append(typeArguments, typeTag) - } - return -} - -func (txb *Transaction) Finish(sender string, gasObject *string, gasBudget uint64, gasPrice *uint64) (*sui_types.TransactionData, []byte, error) { - hexSender, err := sui_types.NewAddressFromHex(sender) - if err != nil { - return nil, nil, err - } - - gasPayment := []*sui_types.ObjectRef{} - if gasObject == nil { - coins, err := txb.client.GetCoins(types.GetCoinsParams{ - Owner: sender, - CoinType: gm.NewStringPtr(SuiGasCoinType), - }) - if err != nil { - return nil, nil, err - } - - for _, coin := range coins.Data { - digest, err := sui_types.NewDigest(coin.Digest) - if err != nil { - return nil, nil, err - } - - uint64Version, err := strconv.ParseUint(coin.Version, 10, 64) - if err != nil { - return nil, nil, err - } - - objectId, err := sui_types.NewObjectIdFromHex(coin.CoinObjectId) - if err != nil { - return nil, nil, err - } - reference := sui_types.ObjectRef{ - Digest: *digest, - Version: uint64Version, - ObjectId: *objectId, - } - gasPayment = append(gasPayment, &reference) - } - } else { - gasObjectId, _, err := GetObjectAndUnmarshal[any](txb.client, *gasObject) - if err != nil { - return nil, nil, err - } - - digest, err := sui_types.NewDigest(gasObjectId.Data.Digest) - if err != nil { - return nil, nil, err - } - - uint64Version, err := strconv.ParseUint(gasObjectId.Data.Version, 10, 64) - if err != nil { - return nil, nil, err - } - - objectId, err := sui_types.NewObjectIdFromHex(gasObjectId.Data.ObjectId) - if err != nil { - return nil, nil, err - } - gasReference := sui_types.ObjectRef{ - Digest: *digest, - Version: uint64Version, - ObjectId: *objectId, - } - gasPayment = append(gasPayment, &gasReference) - } - - var referenceGasPrice uint64 - if gasPrice == nil { - refGasPrice, err := txb.client.GetReferenceGasPrice() - if err != nil { - return nil, nil, err - } - referenceGasPrice = refGasPrice.Uint64() + 1 - } else { - referenceGasPrice = *gasPrice - } - - tx := sui_types.NewProgrammable( - *hexSender, - gasPayment, - txb.builder.Finish(), - gasBudget, - referenceGasPrice, - ) - bs, err := bcs.Marshal(tx) - return &tx, bs, err -} - -func (txb *Transaction) Builder() *sui_types.ProgrammableTransactionBuilder { - return txb.builder -} - -func (txb *Transaction) Client() *client.SuiClient { - return txb.client -} - -func (txb *Transaction) Context() context.Context { - return txb.ctx -} - -// -------- -func isTxContext(param types.SuiMoveNormalizedType) bool { - structType := extractStructTag(param) - if structType == nil { - return false - } - - return structType.Struct.Address == "0x2" && structType.Struct.Module == "tx_context" && structType.Struct.Name == "TxContext" -} - -func extractStructTag(normalizedType types.SuiMoveNormalizedType) *types.SuiMoveNormalizedType_Struct { - _struct, ok := normalizedType.(types.SuiMoveNormalizedType_Struct) - if ok { - return &_struct - } - - ref := extractReference(normalizedType) - mutRef := extractMutableReference(normalizedType) - - if ref != nil { - return extractStructTag(ref) - } - - if mutRef != nil { - return extractStructTag(mutRef) - } - - return nil -} - -func extractReference(normalizedType types.SuiMoveNormalizedType) types.SuiMoveNormalizedType { - reference, ok := normalizedType.(types.SuiMoveNormalizedType_Reference) - if ok { - return reference.Reference.SuiMoveNormalizedType - } - return nil -} - -func extractMutableReference(normalizedType types.SuiMoveNormalizedType) types.SuiMoveNormalizedType { - mutableReference, ok := normalizedType.(types.SuiMoveNormalizedType_MutableReference) - if ok { - return mutableReference.MutableReference.SuiMoveNormalizedType - } - return nil -} diff --git a/gmsui/transactions/internal.go b/gmsui/transactions/internal.go new file mode 100644 index 0000000..99607cb --- /dev/null +++ b/gmsui/transactions/internal.go @@ -0,0 +1,435 @@ +package transactions + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + gm "github.com/W3Tools/go-modules" + "github.com/W3Tools/go-modules/gmsui/types" + "github.com/W3Tools/go-modules/gmsui/utils" + "github.com/W3Tools/go-sui-sdk/v2/lib" + "github.com/W3Tools/go-sui-sdk/v2/move_types" + "github.com/W3Tools/go-sui-sdk/v2/sui_types" +) + +// Create Transaction Result With NormalizedMoveFunction Return Count +func (txb *Transaction) createTransactionResult(count int) []*sui_types.Argument { + nestedResult1 := uint16(len(txb.builder.Commands) - 1) + returnArguments := make([]*sui_types.Argument, count) + for i := 0; i < count; i++ { + returnArguments[i] = &sui_types.Argument{ + NestedResult: &struct { + Result1 uint16 + Result2 uint16 + }{ + Result1: nestedResult1, + Result2: uint16(i), + }, + } + } + + return returnArguments +} + +func setGasPrice(txb *Transaction) error { + if txb.GasConfig.Price == 0 { + referenceGasPrice, err := txb.client.GetReferenceGasPrice() + if err != nil { + return fmt.Errorf("failed to get reference gas price, err: %v", err) + } + + txb.GasConfig.Price = referenceGasPrice.Uint64() + 1 + } + + return nil +} + +func setGasBudget(txb *Transaction) error { + if txb.GasConfig.Budget == 0 { + tx := *txb + + dryRunResult, err := tx.DryRunTransactionBlock() + if err != nil { + return fmt.Errorf("failed to dry run transaction block, err: %v", err) + } + + if dryRunResult.Effects.Status.Status != "success" { + return fmt.Errorf("dry run failed, could not automatically determine a budget: %v", dryRunResult.Effects.Status.Error) + } + + safeOverhead := utils.GAS_SAFE_OVERHEAD * tx.GasConfig.Price + + computationCost, err := strconv.ParseUint(dryRunResult.Effects.GasUsed.ComputationCost, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse computation cost, err: %v", err) + } + baseComputationCostWithOverhead := computationCost + safeOverhead + + storageCost, err := strconv.ParseUint(dryRunResult.Effects.GasUsed.StorageCost, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse storage cost, err: %v", err) + } + storageRebate, err := strconv.ParseUint(dryRunResult.Effects.GasUsed.StorageRebate, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse storage rebate, err: %v", err) + } + + gasBudget := baseComputationCostWithOverhead + storageCost - storageRebate + + if gasBudget > baseComputationCostWithOverhead { + txb.GasConfig.Budget = gasBudget + } else { + txb.GasConfig.Budget = baseComputationCostWithOverhead + } + } + + return nil +} + +func setGasPayment(txb *Transaction) error { + if len(txb.GasConfig.Payment) == 0 { + owner := txb.GasConfig.Owner + if owner == "" { + owner = txb.Sender.String() + } + coins, err := txb.client.GetCoins(types.GetCoinsParams{Owner: owner, CoinType: gm.NewStringPtr(utils.SUI_TYPE_ARG)}) + if err != nil { + return fmt.Errorf("failed to get coins, err: %v", err) + } + + paymentCoins := make([]*sui_types.ObjectRef, 0) + for _, coin := range coins.Data { + objectRef, err := coinStructToObjectRef(coin) + if err != nil { + return fmt.Errorf("failed to create object reference, err: %v", err) + } + + paymentCoins = append(paymentCoins, objectRef) + } + + if len(paymentCoins) == 0 { + return fmt.Errorf("no valid gas coins found for the transaction") + } + + txb.GasConfig.Payment = paymentCoins + } + + return nil +} + +// Check if the param is tx_context.TxContext +func isTxContext(param types.SuiMoveNormalizedType) bool { + structType := extractStructTag(param) + if structType == nil { + return false + } + + return structType.Struct.Address == "0x2" && structType.Struct.Module == "tx_context" && structType.Struct.Name == "TxContext" +} + +// Extract NormalizedMoveFunction Type +func extractStructTag(normalizedType types.SuiMoveNormalizedType) *types.SuiMoveNormalizedType_Struct { + _struct, ok := normalizedType.(types.SuiMoveNormalizedType_Struct) + if ok { + return &_struct + } + + ref := extractReference(normalizedType) + mutRef := extractMutableReference(normalizedType) + + if ref != nil { + return extractStructTag(ref) + } + + if mutRef != nil { + return extractStructTag(mutRef) + } + + return nil +} + +func extractReference(normalizedType types.SuiMoveNormalizedType) types.SuiMoveNormalizedType { + reference, ok := normalizedType.(types.SuiMoveNormalizedType_Reference) + if ok { + return reference.Reference.SuiMoveNormalizedType + } + return nil +} + +func extractMutableReference(normalizedType types.SuiMoveNormalizedType) types.SuiMoveNormalizedType { + mutableReference, ok := normalizedType.(types.SuiMoveNormalizedType_MutableReference) + if ok { + return mutableReference.MutableReference.SuiMoveNormalizedType + } + return nil +} + +// Resolve Parameter + +// Allowed types are sui_types.Argument, string -> object id, *TransactionInputGasCoin +func (txb *Transaction) resolveSplitCoinsCoin(coin any) (*UnresolvedParameter, error) { + unresolvedParameter := NewUnresolvedParameter(1) + + reflectValue := reflect.ValueOf(coin) + switch reflectValue.Type() { + case reflect.TypeOf((*sui_types.Argument)(nil)): // nest result + unresolvedParameter.Arguments[0] = &UnresolvedArgument{Argument: reflectValue.Interface().(*sui_types.Argument)} + case reflect.TypeOf((*TransactionInputGasCoin)(nil)): // gas coin + unresolvedParameter.Arguments[0] = &UnresolvedArgument{Argument: &sui_types.Argument{GasCoin: &lib.EmptyEnum{}}} + case reflect.TypeOf(""): // object id + unresolvedParameter.Objects[0] = UnresolvedObject{Mutable: false, ObjectId: reflectValue.String()} + default: + return nil, fmt.Errorf("input coin should one of address(string), sui_types.Argument or *TransactionInputGasCoin, got %v", reflectValue.Type().String()) + } + + return unresolvedParameter, nil +} + +// Allowed types are uint, uint8, uint16, uint32, uint64, sui_types.Argument +func (txb *Transaction) resolveSplitCoinsAmounts(amounts []any) (*UnresolvedParameter, error) { + unresolvedParameter := NewUnresolvedParameter(len(amounts)) + + for idx, amount := range amounts { + reflectValue := reflect.ValueOf(amount) + switch reflectValue.Type() { + case reflect.TypeOf((*sui_types.Argument)(nil)): // nest result + unresolvedParameter.Arguments[idx] = &UnresolvedArgument{Argument: reflectValue.Interface().(*sui_types.Argument)} + case reflect.TypeOf(uint(0)), reflect.TypeOf(uint8(0)), reflect.TypeOf(uint16(0)), reflect.TypeOf(uint32(0)), reflect.TypeOf(uint64(0)): + unresolvedParameter.Arguments[idx] = &UnresolvedArgument{Pure: amount} + default: + return nil, fmt.Errorf("input amount should be uint or sui_types.Argument at index %d, got %v", idx, reflectValue.Type().String()) + } + } + + return unresolvedParameter, nil +} + +// Parse TransferObjects Params + +// Allowed types are sui_types.Argument, string -> object id +func (txb *Transaction) resolveTransferObjectsObjects(objects []any) (*UnresolvedParameter, error) { + unresolvedParameter := NewUnresolvedParameter(len(objects)) + + for idx, object := range objects { + reflectValue := reflect.ValueOf(object) + switch reflectValue.Type() { + case reflect.TypeOf((*sui_types.Argument)(nil)): // nest result + unresolvedParameter.Arguments[idx] = &UnresolvedArgument{Argument: reflectValue.Interface().(*sui_types.Argument)} + case reflect.TypeOf((*TransactionInputGasCoin)(nil)): // gas coin + unresolvedParameter.Arguments[idx] = &UnresolvedArgument{Argument: &sui_types.Argument{GasCoin: &lib.EmptyEnum{}}} + case reflect.TypeOf(""): // object id + unresolvedParameter.Objects[idx] = UnresolvedObject{Mutable: false, ObjectId: reflectValue.String()} + default: + return nil, fmt.Errorf("input object should one of address(string), sui_types.Argument or *TransactionInputGasCoin at index %d, got %v", idx, reflectValue.Type().String()) + } + } + + return unresolvedParameter, nil +} + +// Allowed types are sui_types.Argument, string -> address +func (txb *Transaction) resolveTransferObjectsAddress(address any) (*UnresolvedParameter, error) { + unresolvedParameter := NewUnresolvedParameter(1) + + reflectValue := reflect.ValueOf(address) + switch reflectValue.Type() { + case reflect.TypeOf((*sui_types.Argument)(nil)): // nest result + unresolvedParameter.Arguments[0] = &UnresolvedArgument{Argument: reflectValue.Interface().(*sui_types.Argument)} + case reflect.TypeOf(""): // address + suiAddress, err := sui_types.NewAddressFromHex(reflectValue.String()) + if err != nil { + return nil, fmt.Errorf("input address must conform to the address(string), got %v", reflectValue.String()) + } + + unresolvedParameter.Arguments[0] = &UnresolvedArgument{Pure: suiAddress} + default: + return nil, fmt.Errorf("input address should be address(string) or sui_types.Argument, got %v", reflectValue.Type().String()) + } + + return unresolvedParameter, nil +} + +// Parse MergeCoins Params + +// Allowed types are sui_types.Argument, string -> object id +func (txb *Transaction) resolveMergeCoinsDestination(destination any) (*UnresolvedParameter, error) { + unresolvedParameter := NewUnresolvedParameter(1) + + reflectValue := reflect.ValueOf(destination) + switch reflectValue.Type() { + case reflect.TypeOf((*sui_types.Argument)(nil)): // nest result + unresolvedParameter.Arguments[0] = &UnresolvedArgument{Argument: reflectValue.Interface().(*sui_types.Argument)} + case reflect.TypeOf(""): // address + unresolvedParameter.Objects[0] = UnresolvedObject{Mutable: false, ObjectId: reflectValue.String()} + default: + return nil, fmt.Errorf("input destination should be address(string) or sui_types.Argument, got %v", reflectValue.Type().String()) + } + + return unresolvedParameter, nil +} + +// Allowed types are sui_types.Argument, string -> object id +func (txb *Transaction) resolveMergeCoinsSources(sources []any) (*UnresolvedParameter, error) { + unresolvedParameter := NewUnresolvedParameter(len(sources)) + + for idx, source := range sources { + reflectValue := reflect.ValueOf(source) + switch reflectValue.Type() { + case reflect.TypeOf((*sui_types.Argument)(nil)): // nest result + unresolvedParameter.Arguments[idx] = &UnresolvedArgument{Argument: reflectValue.Interface().(*sui_types.Argument)} + case reflect.TypeOf(""): // object id + unresolvedParameter.Objects[idx] = UnresolvedObject{Mutable: false, ObjectId: reflectValue.String()} + default: + return nil, fmt.Errorf("input source should be address(string) or sui_types.Argument at index %d, got %v", idx, reflectValue.Type().String()) + } + } + + return unresolvedParameter, nil +} + +// Resolve Function +func (txb *Transaction) resolveMoveFunction(pkg, mod, fn string, arguments []interface{}, typeArguments []string) (inputArguments []sui_types.Argument, inputTypeArguments []move_types.TypeTag, returnsCount int, err error) { + normalized, err := txb.client.GetNormalizedMoveFunction(types.GetNormalizedMoveFunctionParams{Package: pkg, Module: mod, Function: fn}) + if err != nil { + return nil, nil, 0, fmt.Errorf("can not call jsonrpc to get normalized move function in command %d: %v", len(txb.builder.Commands), err) + } + + if len(normalized.Parameters) > 0 && isTxContext(normalized.Parameters[len(normalized.Parameters)-1].SuiMoveNormalizedType) { + normalized.Parameters = normalized.Parameters[:len(arguments)] + } + + if len(arguments) != len(normalized.Parameters) || len(typeArguments) != len(normalized.TypeParameters) { + return nil, nil, 0, fmt.Errorf("incorrect number of arguments or type arguments in command %d, required arguments: %d, type arguments: %d", len(txb.builder.Commands), len(normalized.Parameters), len(normalized.TypeParameters)) + } + + inputTypeArguments, err = txb.resolveFunctionTypeArguments(typeArguments) + if err != nil { + return nil, nil, 0, fmt.Errorf("can not resolve function type arguments in command %d: %v", len(txb.builder.Commands), err) + } + + unresolvedParameter, err := txb.resolveFunctionArguments(arguments, normalized.Parameters) + if err != nil { + return nil, nil, 0, fmt.Errorf("can not resolve function arguments in command %d: %v", len(txb.builder.Commands), err) + } + + inputArguments, err = unresolvedParameter.resolveAndPArseToArguments(txb.client, txb) + if err != nil { + return nil, nil, 0, fmt.Errorf("can not parse unresolved parameter to arguments in command %d: %v", len(txb.builder.Commands), err) + } + return inputArguments, inputTypeArguments, len(normalized.Return), nil +} + +func (txb *Transaction) resolveFunctionArguments(inputArguments []interface{}, requiredArguments []*types.SuiMoveNormalizedTypeWrapper) (*UnresolvedParameter, error) { + unresolvedParameter := NewUnresolvedParameter(len(requiredArguments)) + for idx, parameter := range requiredArguments { + reflecetInput := reflect.ValueOf(inputArguments[idx]) + if reflecetInput.Type() == reflect.TypeOf((*sui_types.Argument)(nil)) { + unresolvedParameter.Arguments[idx] = &UnresolvedArgument{Argument: reflecetInput.Interface().(*sui_types.Argument)} + continue + } + + reflectParameter := reflect.ValueOf(parameter.SuiMoveNormalizedType) + + switch reflectParameter.Type() { + case reflect.TypeOf(types.SuiMoveNormalizedType_Vector{}): + unresolvedParameter.Arguments[idx] = &UnresolvedArgument{Pure: inputArguments[idx]} + case reflect.TypeOf(types.SuiMoveNormalizedType_String("")): + // Here we are only supporting pure types + switch reflectParameter.String() { + case "Bool", "U8", "U16", "U32", "U64", "U128", "U256": + if reflecetInput.Kind() == reflect.String { + return nil, fmt.Errorf("input parameter must be bool or unsigned integer at index %d, got %v", idx, reflecetInput.Type()) + } + unresolvedParameter.Arguments[idx] = &UnresolvedArgument{Pure: inputArguments[idx]} + case "Address": + address, err := sui_types.NewAddressFromHex(utils.NormalizeSuiAddress(inputArguments[idx].(string))) + if err != nil { + return nil, fmt.Errorf("input parameter must conform to the address(string) at index %d, got %v", idx, inputArguments[idx]) + } + unresolvedParameter.Arguments[idx] = &UnresolvedArgument{Pure: address} + default: + return nil, fmt.Errorf("function string parameter [%v] is not supported at index %d", reflectParameter.String(), idx) + } + default: + if reflecetInput.Type().Kind() != reflect.String { + return nil, fmt.Errorf("input parameter must be address(string) at index %d, got %v", idx, reflecetInput.Type()) + } + switch reflectParameter.Type() { + case reflect.TypeOf(types.SuiMoveNormalizedType_Reference{}): + unresolvedParameter.Objects[idx] = UnresolvedObject{Mutable: false, ObjectId: inputArguments[idx].(string)} + case reflect.TypeOf(types.SuiMoveNormalizedType_MutableReference{}): + unresolvedParameter.Objects[idx] = UnresolvedObject{Mutable: true, ObjectId: inputArguments[idx].(string)} + default: + return nil, fmt.Errorf("function parameter [%v] is not supported at index %d", reflectParameter.Type(), idx) + } + } + } + return unresolvedParameter, nil +} + +func (txb *Transaction) resolveFunctionTypeArguments(typeArguments []string) (inputTypeArguments []move_types.TypeTag, err error) { + inputTypeArguments = []move_types.TypeTag{} + + for idx, arg := range typeArguments { + entry := strings.Split(arg, "::") + if len(entry) != 3 { + return nil, fmt.Errorf("input type arguments at index %d must be in the format 'address::module::name', got [%v]", idx, arg) + } + + object, err := sui_types.NewObjectIdFromHex(entry[0]) + if err != nil { + return nil, fmt.Errorf("input type arguments at index %d must be in the format 'address::module::name', got [%v]: %v", idx, entry[0], err) + } + + typeTag := move_types.TypeTag{ + Struct: &move_types.StructTag{ + Address: *object, + Module: move_types.Identifier(entry[1]), + Name: move_types.Identifier(entry[2]), + }, + } + inputTypeArguments = append(inputTypeArguments, typeTag) + } + return +} + +// Convert ObjectResponse to ObjectArg +func objectResponseToObjectArg(data *types.SuiObjectResponse, mutable bool) (*sui_types.ObjectArg, error) { + if data == nil || data.Data == nil { + return nil, fmt.Errorf("invalid object response") + } + + objectArg := new(sui_types.ObjectArg) + + id, err := sui_types.NewObjectIdFromHex(data.Data.ObjectId) + if err != nil { + return nil, fmt.Errorf("object id [%s] must be sui address: %v", data.Data.ObjectId, err) + } + + owner := data.Data.Owner.ObjectOwner + switch owner := owner.(type) { + case types.ObjectOwner_Shared: + // Shared object: just set the initial shared version + objectArg.SharedObject = &struct { + Id move_types.AccountAddress + InitialSharedVersion uint64 + Mutable bool + }{ + Id: *id, + InitialSharedVersion: owner.Shared.InitialSharedVersion, + Mutable: mutable, + } + default: + // Other object: set the version and digest + objectRef, err := ObjectStringRef{ObjectId: data.Data.ObjectId, Version: data.Data.Version, Digest: data.Data.Digest}.toObjectRef() + if err != nil { + return nil, fmt.Errorf("can not convert object ref: %v", err) + } + + objectArg.ImmOrOwnedObject = objectRef + } + + return objectArg, nil +} diff --git a/gmsui/transactions/object_ref.go b/gmsui/transactions/object_ref.go new file mode 100644 index 0000000..fb8b86c --- /dev/null +++ b/gmsui/transactions/object_ref.go @@ -0,0 +1,36 @@ +package transactions + +import ( + "fmt" + "strconv" + + "github.com/W3Tools/go-modules/gmsui/types" + "github.com/W3Tools/go-sui-sdk/v2/sui_types" +) + +type ObjectStringRef struct { + ObjectId string `json:"objectId"` + Version string `json:"version"` + Digest string `json:"digest"` +} + +func (ref ObjectStringRef) toObjectRef() (*sui_types.ObjectRef, error) { + objectId, err := sui_types.NewObjectIdFromHex(ref.ObjectId) + if err != nil { + return nil, fmt.Errorf("can not create object id from hex [%s]: %v", ref.ObjectId, err) + } + digest, err := sui_types.NewDigest(ref.Digest) + if err != nil { + return nil, fmt.Errorf("can not create digest [%s]: %v", ref.Digest, err) + } + version, err := strconv.ParseUint(ref.Version, 10, 64) + if err != nil { + return nil, fmt.Errorf("can not parse version [%s] to uint64: %v", ref.Version, err) + } + + return &sui_types.ObjectRef{ObjectId: *objectId, Version: version, Digest: *digest}, nil +} + +func coinStructToObjectRef(coin types.CoinStruct) (*sui_types.ObjectRef, error) { + return ObjectStringRef{ObjectId: coin.CoinObjectId, Version: coin.Version, Digest: coin.Digest}.toObjectRef() +} diff --git a/gmsui/transactions/parameter.go b/gmsui/transactions/parameter.go new file mode 100644 index 0000000..4cde2a4 --- /dev/null +++ b/gmsui/transactions/parameter.go @@ -0,0 +1,123 @@ +package transactions + +import ( + "fmt" + + "github.com/W3Tools/go-modules/gmsui/client" + "github.com/W3Tools/go-modules/gmsui/types" + "github.com/W3Tools/go-modules/gmsui/utils" + "github.com/W3Tools/go-sui-sdk/v2/sui_types" +) + +type UnresolvedParameter struct { + Arguments UnresolvedArguments `json:"argument"` + Objects map[int]UnresolvedObject `json:"object"` // map key is index of argument +} + +type UnresolvedArgument struct { + Pure any + Object *sui_types.ObjectArg + Argument *sui_types.Argument +} +type UnresolvedArguments []*UnresolvedArgument + +type UnresolvedObject struct { + ObjectId string + Mutable bool +} + +func NewUnresolvedParameter(count int) *UnresolvedParameter { + return &UnresolvedParameter{ + Arguments: make(UnresolvedArguments, count), + Objects: make(map[int]UnresolvedObject), + } +} + +func (up *UnresolvedParameter) merge(dest *UnresolvedParameter) { + if dest == nil { + return + } + + count := len(up.Arguments) + + up.Arguments = append(up.Arguments, dest.Arguments...) + for idx, obj := range dest.Objects { + up.Objects[idx+count] = obj + } +} + +func (up *UnresolvedParameter) resolveAndPArseToArguments(suiClient *client.SuiClient, txb *Transaction) ([]sui_types.Argument, error) { + err := up.resolveObjects(suiClient) + if err != nil { + return nil, fmt.Errorf("can not resolve objects: %v", err) + } + + return up.toArguments(txb) +} + +func (up *UnresolvedParameter) resolveObjects(suiClient *client.SuiClient) error { + if len(up.Objects) > 0 { + var ids []string + for _, resolve := range up.Objects { + ids = append(ids, resolve.ObjectId) + } + + var objects []*types.SuiObjectResponse + objects, err := suiClient.MultiGetObjects(types.MultiGetObjectsParams{IDs: ids, Options: &types.SuiObjectDataOptions{ShowOwner: true}}) + if err != nil { + return fmt.Errorf("can not call jsonrpc to multi get objects: %v", err) + } + + objectMap := utils.SliceToMap(objects, func(v *types.SuiObjectResponse) string { + if v.Data != nil { + return v.Data.ObjectId + } + return "" + }) + + for idx, resolveObject := range up.Objects { + if idx >= len(up.Arguments) { + return fmt.Errorf("can not resolve object at index %d, out of range", idx) + } + + object := objectMap[utils.NormalizeSuiObjectId(resolveObject.ObjectId)] + if object == nil { + return fmt.Errorf("can not fetch object with id [%s] at index %d", resolveObject.ObjectId, idx) + } + + objectArg, err := objectResponseToObjectArg(object, resolveObject.Mutable) + if err != nil { + return fmt.Errorf("can not convert object response to object arg at index %d: %v", idx, err) + } + + up.Arguments[idx] = &UnresolvedArgument{Object: objectArg} + } + } + + return nil +} + +func (up *UnresolvedParameter) toArguments(txb *Transaction) ([]sui_types.Argument, error) { + arguments := make([]sui_types.Argument, len(up.Arguments)) + for idx, input := range up.Arguments { + if input.Pure != nil { + value, err := txb.builder.Pure(input.Pure) + if err != nil { + return nil, fmt.Errorf("can not create pure argument at index %d: %v", idx, err) + } + arguments[idx] = value + } else if input.Object != nil { + value, err := txb.builder.Obj(*input.Object) + if err != nil { + return nil, fmt.Errorf("can not create object argument at index %d: %v", idx, err) + } + arguments[idx] = value + } else if input.Argument != nil { + arguments[idx] = *input.Argument + } else { + return nil, fmt.Errorf("invalid input argument at index: %v, pure: %v, object: %v, argument: %v", idx, input.Pure, input.Object, input.Argument) + } + } + + return arguments, nil +} diff --git a/gmsui/transactions/transaction.go b/gmsui/transactions/transaction.go new file mode 100644 index 0000000..78be25f --- /dev/null +++ b/gmsui/transactions/transaction.go @@ -0,0 +1,291 @@ +package transactions + +import ( + "context" + "fmt" + "strings" + + "github.com/W3Tools/go-modules/gmsui/client" + "github.com/W3Tools/go-modules/gmsui/types" + "github.com/W3Tools/go-modules/gmsui/utils" + "github.com/W3Tools/go-sui-sdk/v2/move_types" + "github.com/W3Tools/go-sui-sdk/v2/sui_types" + "github.com/fardream/go-bcs/bcs" +) + +type Transaction struct { + client *client.SuiClient + builder *sui_types.ProgrammableTransactionBuilder + ctx context.Context + + Sender *sui_types.SuiAddress `json:"sender"` + GasConfig *GasData `json:"gasConfig"` +} + +type GasData struct { + Budget uint64 `json:"budget"` + Price uint64 `json:"price"` + Owner string `json:"owner"` + Payment []*sui_types.ObjectRef `json:"payment"` +} + +func NewTransaction(client *client.SuiClient) *Transaction { + return &Transaction{ + client: client, + builder: sui_types.NewProgrammableTransactionBuilder(), + ctx: client.Context(), + + GasConfig: new(GasData), + } +} + +func (txb *Transaction) TransactionBuilder() *sui_types.ProgrammableTransactionBuilder { + return txb.builder +} + +func (txb *Transaction) Client() *client.SuiClient { + return txb.client +} + +func (txb *Transaction) Context() context.Context { + return txb.ctx +} + +type TransactionInputGasCoin struct { + GasCoin bool `json:"gasCoin"` +} + +func (txb *Transaction) Gas() *TransactionInputGasCoin { + return &TransactionInputGasCoin{GasCoin: true} +} + +// Split coins into multiple parts +func (txb *Transaction) SplitCoins(coin interface{}, amounts []interface{}) (returnArguments []*sui_types.Argument, err error) { + if len(amounts) == 0 { + return nil, nil + } + + unresolvedParameter, err := txb.resolveSplitCoinsCoin(coin) + if err != nil { + return nil, fmt.Errorf("can not resolve coin in command %d: %v", len(txb.builder.Commands), err) + } + + amountArguments, err := txb.resolveSplitCoinsAmounts(amounts) + if err != nil { + return nil, fmt.Errorf("can not resolve amounts in command %d: %v", len(txb.builder.Commands), err) + } + + unresolvedParameter.merge(amountArguments) + arguments, err := unresolvedParameter.resolveAndPArseToArguments(txb.client, txb) + if err != nil { + return nil, fmt.Errorf("can not resolve and parse to arguments in command %d, err: %v", len(txb.builder.Commands), err) + } + + txb.builder.Command( + sui_types.Command{ + SplitCoins: &struct { + Argument sui_types.Argument + Arguments []sui_types.Argument + }{ + Argument: arguments[0], + Arguments: arguments[1:], + }, + }, + ) + + return txb.createTransactionResult(len(amounts)), nil +} + +// Transfer objects to address +func (txb *Transaction) TransferObjects(objects []interface{}, address interface{}) error { + if len(objects) == 0 { + return nil + } + + unresolvedParameter, err := txb.resolveTransferObjectsObjects(objects) + if err != nil { + return fmt.Errorf("can not resolve objects in command %d: %v", len(txb.builder.Commands), err) + } + + unresolvedAddressArgument, err := txb.resolveTransferObjectsAddress(address) + if err != nil { + return fmt.Errorf("can not resolve address in command %d: %v", len(txb.builder.Commands), err) + } + + unresolvedParameter.merge(unresolvedAddressArgument) + arguments, err := unresolvedParameter.resolveAndPArseToArguments(txb.client, txb) + if err != nil { + return fmt.Errorf("can not resolve and parse to arguments in command %d, err: %v", len(txb.builder.Commands), err) + } + + txb.builder.Command( + sui_types.Command{ + TransferObjects: &struct { + Arguments []sui_types.Argument + Argument sui_types.Argument + }{ + Arguments: arguments[:len(arguments)-1], + Argument: arguments[len(arguments)-1], + }, + }, + ) + + return nil +} + +// Merge Coins into one +func (txb *Transaction) MergeCoins(destination interface{}, sources []interface{}) (err error) { + if len(sources) == 0 { + return nil + } + + unresolvedParameter, err := txb.resolveMergeCoinsDestination(destination) + if err != nil { + return fmt.Errorf("failed to resolve destination in command %d, err: %v", len(txb.builder.Commands), err) + } + + unresolvedSourceParameter, err := txb.resolveMergeCoinsSources(sources) + if err != nil { + return fmt.Errorf("failed to resolve sources in command %d, err: %v", len(txb.builder.Commands), err) + } + + unresolvedParameter.merge(unresolvedSourceParameter) + arguments, err := unresolvedParameter.resolveAndPArseToArguments(txb.client, txb) + if err != nil { + return fmt.Errorf("can not resolve and parse to arguments in command %d, err: %v", len(txb.builder.Commands), err) + } + + txb.builder.Command( + sui_types.Command{ + MergeCoins: &struct { + Argument sui_types.Argument + Arguments []sui_types.Argument + }{ + Argument: arguments[0], + Arguments: arguments[1:], + }, + }, + ) + + return nil +} + +func (txb *Transaction) MoveCall(target string, arguments []interface{}, typeArguments []string) (returnArguments []*sui_types.Argument, err error) { + entry := strings.Split(target, "::") + if len(entry) != 3 { + return nil, fmt.Errorf("invalid target [%s]", target) + } + var pkg, mod, fn = utils.NormalizeSuiObjectId(entry[0]), entry[1], entry[2] + + inputArguments, inputTypeArguments, returnsCount, err := txb.resolveMoveFunction(pkg, mod, fn, arguments, typeArguments) + if err != nil { + return nil, fmt.Errorf("failed to parse function arguments, err: %v", err) + } + + packageId, err := sui_types.NewAddressFromHex(pkg) + if err != nil { + return nil, fmt.Errorf("invalid package address [%v]", err) + } + + txb.builder.Command( + sui_types.Command{ + MoveCall: &sui_types.ProgrammableMoveCall{ + Package: *packageId, + Module: move_types.Identifier(mod), + Function: move_types.Identifier(fn), + Arguments: inputArguments, + TypeArguments: inputTypeArguments, + }, + }, + ) + + return txb.createTransactionResult(returnsCount), nil +} + +func (txb *Transaction) Build(sender string) (*sui_types.TransactionData, []byte, error) { + txb.SetSenderIfNotSet(sender) + + if err := setGasPrice(txb); err != nil { + return nil, nil, fmt.Errorf("can not set gas price when building transaction: %v", err) + } + if err := setGasBudget(txb); err != nil { + return nil, nil, fmt.Errorf("can not set gas budget when building transaction: %v", err) + } + if err := setGasPayment(txb); err != nil { + return nil, nil, fmt.Errorf("can not set gas payment when building transaction: %v", err) + } + + tx := sui_types.NewProgrammable( + *txb.Sender, + txb.GasConfig.Payment, + txb.builder.Finish(), + txb.GasConfig.Budget, + txb.GasConfig.Price, + ) + bs, err := bcs.Marshal(tx) + if err != nil { + return nil, nil, fmt.Errorf("can not marshal transaction: %v", err) + } + return &tx, bs, err +} + +func (txb *Transaction) DryRunTransactionBlock() (*types.DryRunTransactionBlockResponse, error) { + if txb.Sender == nil { + return nil, fmt.Errorf("missing transaction sender") + } + + if err := setGasPrice(txb); err != nil { + return nil, fmt.Errorf("failed to set gas price, err: %v", err) + } + + tx := sui_types.NewProgrammable( + *txb.Sender, + nil, + txb.builder.Finish(), + utils.MAX_GAS, + txb.GasConfig.Price, + ) + bs, err := bcs.Marshal(tx) + if err != nil { + return nil, fmt.Errorf("failed to marshal transaction, err: %v", err) + } + + return txb.client.DryRunTransactionBlock(types.DryRunTransactionBlockParams{TransactionBlock: bs}) +} + +func (txb *Transaction) SetSender(sender string) { + address, err := sui_types.NewAddressFromHex(sender) + if err != nil { + panic(fmt.Errorf("failed to create address from hex [%s], err: %v", sender, err)) + } + + txb.Sender = address +} + +func (txb *Transaction) SetSenderIfNotSet(sender string) { + if txb.Sender == nil { + txb.SetSender(sender) + } +} + +func (txb *Transaction) SetGasPrice(price uint64) { + txb.GasConfig.Price = price +} + +func (txb *Transaction) SetGasBudget(budget uint64) { + txb.GasConfig.Budget = budget +} + +func (txb *Transaction) SetGasBudgetIfNotSet(budget uint64) { + if txb.GasConfig.Budget == 0 { + txb.GasConfig.Budget = budget + } +} + +func (txb *Transaction) SetGasOwner(owner string) { + txb.GasConfig.Owner = owner +} + +func (txb *Transaction) SetGasPayment(payments []*sui_types.ObjectRef) { + txb.GasConfig.Payment = payments +} diff --git a/gmsui/utils/constants.go b/gmsui/utils/constants.go index d048613..6bb743b 100644 --- a/gmsui/utils/constants.go +++ b/gmsui/utils/constants.go @@ -3,14 +3,15 @@ package utils import "fmt" var ( - SUI_DECIMALS = 9 - MIST_PER_SUI = 1000000000 - - MOVE_STDLIB_ADDRESS = NormalizeSuiObjectId("0x1") - SUI_FRAMEWORK_ADDRESS = NormalizeSuiObjectId("0x2") - SUI_SYSTEM_ADDRESS = NormalizeSuiObjectId("0x3") - SUI_CLOCK_OBJECT_ID = NormalizeSuiObjectId("0x6") - SUI_SYSTEM_MODULE_NAME = "sui_system" - SUI_TYPE_ARG = fmt.Sprintf("%s::sui::SUI", SUI_FRAMEWORK_ADDRESS) - SUI_SYSTEM_STATE_OBJECT_ID = NormalizeSuiObjectId("0x5") + SUI_DECIMALS = 9 + MIST_PER_SUI = 1000000000 + GAS_SAFE_OVERHEAD uint64 = 1000 + MAX_GAS uint64 = 50000000000 + MOVE_STDLIB_ADDRESS = NormalizeSuiObjectId("0x1") + SUI_FRAMEWORK_ADDRESS = NormalizeSuiObjectId("0x2") + SUI_SYSTEM_ADDRESS = NormalizeSuiObjectId("0x3") + SUI_CLOCK_OBJECT_ID = NormalizeSuiObjectId("0x6") + SUI_SYSTEM_MODULE_NAME = "sui_system" + SUI_TYPE_ARG = fmt.Sprintf("%s::sui::SUI", SUI_FRAMEWORK_ADDRESS) + SUI_SYSTEM_STATE_OBJECT_ID = NormalizeSuiObjectId("0x5") ) diff --git a/gmsui/utils/slice2map.go b/gmsui/utils/slice2map.go new file mode 100644 index 0000000..751a401 --- /dev/null +++ b/gmsui/utils/slice2map.go @@ -0,0 +1,9 @@ +package utils + +func SliceToMap[T any, V comparable](src []T, key func(T) V) map[V]T { + var result = make(map[V]T) + for _, v := range src { + result[key(v)] = v + } + return result +} From 254e2d0c67a92fbe4545b7647285b62a4d807804 Mon Sep 17 00:00:00 2001 From: wuuuk Date: Sat, 18 Jan 2025 16:42:51 +0800 Subject: [PATCH 4/5] update dev inspect --- gmsui/transactions/transaction.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/gmsui/transactions/transaction.go b/gmsui/transactions/transaction.go index 78be25f..77ccbe9 100644 --- a/gmsui/transactions/transaction.go +++ b/gmsui/transactions/transaction.go @@ -247,12 +247,26 @@ func (txb *Transaction) DryRunTransactionBlock() (*types.DryRunTransactionBlockR ) bs, err := bcs.Marshal(tx) if err != nil { - return nil, fmt.Errorf("failed to marshal transaction, err: %v", err) + return nil, fmt.Errorf("can not marshal transaction, err: %v", err) } return txb.client.DryRunTransactionBlock(types.DryRunTransactionBlockParams{TransactionBlock: bs}) } +func (txb *Transaction) DevInspectTransactionBlock() (*types.DevInspectResults, error) { + if txb.Sender == nil { + return nil, fmt.Errorf("missing transaction sender") + } + + bs, err := bcs.Marshal(txb.builder.Finish()) + if err != nil { + return nil, fmt.Errorf("can not marshal transaction: %v", err) + } + + txBytes := append([]byte{0}, bs...) + return txb.client.DevInspectTransactionBlock(types.DevInspectTransactionBlockParams{Sender: txb.Sender.String(), TransactionBlock: txBytes}) +} + func (txb *Transaction) SetSender(sender string) { address, err := sui_types.NewAddressFromHex(sender) if err != nil { From 7616e7dea612a91ab9393bc7ee0b5128ccff7118 Mon Sep 17 00:00:00 2001 From: wuuuk Date: Sat, 18 Jan 2025 16:43:14 +0800 Subject: [PATCH 5/5] update dev inspect --- gmsui/dev_inspect.go | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 gmsui/dev_inspect.go diff --git a/gmsui/dev_inspect.go b/gmsui/dev_inspect.go deleted file mode 100644 index 9f7b204..0000000 --- a/gmsui/dev_inspect.go +++ /dev/null @@ -1,28 +0,0 @@ -package gmsui - -import ( - "github.com/W3Tools/go-modules/gmsui/client" - "github.com/W3Tools/go-modules/gmsui/types" - "github.com/fardream/go-bcs/bcs" -) - -func DevInspect(suiClient *client.SuiClient, target string, args []interface{}, typeArgs []string) (*types.DevInspectResults, error) { - builder := NewTransaction(suiClient) - - _, err := builder.NewMoveCall(target, args, typeArgs) - if err != nil { - return nil, err - } - - tx := builder.builder.Finish() - bs, err := bcs.Marshal(tx) - if err != nil { - return nil, err - } - - txBytes := append([]byte{0}, bs...) - return suiClient.DevInspectTransactionBlock(types.DevInspectTransactionBlockParams{ - Sender: SuiZeroAddress, - TransactionBlock: txBytes, - }) -}