Passing struct to smart contract arg via moveCall

Hi everyone,

I’m facing an issue while working with a smart contract interface and attempting to create transaction data using the unsafe_moveCall Sui method. I suspect there might be an issue with my syntax.

Smart contract is:

mainnet Sui
id: 0xba153169476e8c3114962261d1edc70de5ad9781b83cc617ecc8c1923191cae0
module: router
function: swap_exact_x_to_y_direct

The smart contract method I’m trying to use is as follows:

public swap_exact_x_to_y_direct<Ty0, Ty1>(
    Arg0: &mut PairMetadata<Ty0, Ty1>,
    Arg1: Coin<Ty0>,
    Arg2: &mut TxContext): Coin<Ty1>
{
        ...
}

As an example, the PairMetadata<Ty0, Ty1> struct associated with this method is defined as:

struct PairMetadata<phantom Ty0, phantom Ty1> has store, key {
    id: UID,
    reserve_x: Coin<Ty0>,
    reserve_y: Coin<Ty1>,
    k_last: u128,
   lp_supply: Supply<LP<Ty0, Ty1>>,
   fee_rate: u64
}

I’m struggling to pass a struct to one of the arguments of the swap_method. Could someone please assist me with the correct syntax and provide an example of how to pass a struct to some smart contract method?

I use something data as follows and got “{“code”:-32000,“message”:“ObjectID hex string must start with 0x.”}”

	"id": "0x0873c37a0d62911e26bb57f2d8a574f11d1385c89509a55e4c866500b2c54bae",
	"reserve_x": {
		"id": "0x0000000000000000000000000000000000000000000000000000000000000002::suiSUI",
		"balance": 12345
	},
	"reserve_y": {
		"id":"0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN",
		"balance": 1234
	},
	"k_last": 123456,
	"lp_supply": {
		"type": "0x2::balance::Supply<0xba153169476e8c3114962261d1edc70de5ad9781b83cc617ecc8c1923191cae0::pair::LP<0x2::sui::SUI, 0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>>",
		"fields": {
			"value": "69586987607386"
		}
	},
	"fee_rate": 30

Thank you in advance for your help!

2 Likes

It looks like you already have a particular instance of PairMetadata in mind that you want to pass in, which has the following ID:

0x0873c37a0d62911e26bb57f2d8a574f11d1385c89509a55e4c866500b2c54bae

If that’s the case, it’s enough to pass the ID of the object to the call to unsafe_moveCall. It’s not possible to pass a struct by value as a transaction input. The module that defines the struct gets to define how they are constructed, to make sure that only valid instances of that type can exist. E.g. we wouldn’t want someone to be able to create a brand new Coin<SUI> with an arbitrary balance, as a transaction input – we want to make sure it comes from the fixed supply.

If you need to create an instance of the type in order to run the transaction, then you need to look at the module that defined the type, and see which functions it offers for creating a new instance of that type, and what inputs they require (if the inputs are structs themselves, then repeat the process for those structs).

Eventually you’ll find the set of function calls you need to make to make an instance of that struct, and which primitive inputs (numbers, bools, addresses, IDs, strings, or optional or vector types of primitives) that you need to supply to make those calls. At that point you can construct a Programmable Transaction Block that makes all the necessary calls to create an instance of your type, and then finally makes the original function call you wanted to make. You can read more about that here:

3 Likes

Hello! Thank you for your response. I appreciate your feedback.

I’ve taken note of your suggestion, and I’m currently exploring alternative contract methods that involve primitive inputs to construct struct input parameters. However, I encountered an issue with the following error message:

“code”:-32602,“message”:“byte deserialization failed, cause by: AccountAddressParseError”

It appears that there might be an issue with my Signer field. I’m currently using the same Signer value that works for “unsafe_transferSui”. Here’s an example of a successfully passed transaction for reference: trx.

Any insights or guidance on resolving this issue would be greatly appreciated!

2 Likes

Can you share the exact RPC command you are using that generated that error? It’s very likely to be an issue parsing your signer address, but it is impossible to know for sure without seeing the failing transaction.

2 Likes

Thank you for your response.

TransferSui Method
Request:
Endpoint: https://sui-mainnet-endpoint.blockvision.org
Json body:

{
  "jsonrpc": "2.0",
  "method": "unsafe_transferSui",
  "params": [
    "0x6356f0141b7a3dd839279ded06ba9e55d928d7019eff01ca981aba44e31afa96",
    "0x050dba971a34bc495dd6ae34c0554644f435639ea228cfdc6d321ee307abcb15",
    "100000000",
    "0xd8e9c0536645497f01ba267609492bec58fd01f49569c3dec42fc38963fb66f7",
    "1"
  ],
  "id": 1
}

Response:

{
  "jsonrpc": "2.0",
  "result": {
    "txBytes": "AAACACDY6cBTZkVJfwG6JnYJSSvsWP0B9JVpw97EL8OJY/tm9wAIAQ...", 
    "gas": [
      {
        "objectId": "0x050dba971a34bc495dd6ae34c0554644f435639ea228cfdc6d321ee307abcb15",
        "version": 62033192,
        "digest": "5tqXx17u2xhHM6qXucKE6vHRk2XTFp7ZfiEgjVc6EBpD"
      }
    ],
    "inputObjects": [
      {
        "ImmOrOwnedMoveObject": {
          "objectId": "0x050dba971a34bc495dd6ae34c0554644f435639ea228cfdc6d321ee307abcb15",
          "version": 62033192,
          "digest": "5tqXx17u2xhHM6qXucKE6vHRk2XTFp7ZfiEgjVc6EBpD"
        }
      }
    ]
  },
  "id": 1
}

It seems everything is in order, and I can proceed to sign and execute the transaction.

Smart Contract Method with No Arguments:
Request:
Endpoint: https://sui-mainnet-endpoint.blockvision.org
Json body:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "unsafe_moveCall",
  "params": [
    "0x6356f0141b7a3dd839279ded06ba9e55d928d7019eff01ca981aba44e31afa96",
    "0xba153169476e8c3114962261d1edc70de5ad9781b83cc617ecc8c1923191cae0",
    "pair",
    "get_lp_name",
    [],
    [],
    "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI 1000"
  ]
}

Response:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32602,
    "message": "byte deserialization failed, cause by: AccountAddressParseError"
  },
  "id": 1
}

The same issue occurs when attempting to interact with the method using primitive arguments.

Smart Contract Method with Primitive Inputs:
Request:
Endpoint: https://sui-mainnet-endpoint.blockvision.org
Json body:

{
  "jsonrpc": "2.0",
  "method": "unsafe_moveCall",
  "params": [
    "0x6356f0141b7a3dd839279ded06ba9e55d928d7019eff01ca981aba44e31afa96",
    "0xba153169476e8c3114962261d1edc70de5ad9781b83cc617ecc8c1923191cae0",
    "math",
    "max",
    [],
    [
      123,
      456
    ],
    "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI 1000"
  ],
  "id": 1
}

Response:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32602,
    "message": "byte deserialization failed, cause by: AccountAddressParseError"
  },
  "id": 1
}

I am receiving the same error code for both cases. Thank you.

2 Likes

Thanks for the context @pavel, I’ll focus on the first call that errors, because I think the issue is similar in both. The problem is almost certainly with the last argument:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "unsafe_moveCall",
  "params": [
    "0x6356f0141b7a3dd839279ded06ba9e55d928d7019eff01ca981aba44e31afa96",
    "0xba153169476e8c3114962261d1edc70de5ad9781b83cc617ecc8c1923191cae0",
    "pair",
    "get_lp_name",
    [],
    [],
    "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI 1000"
  ]
}

"0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI 1000" is not a valid parameter. If we look at the docs for unsafe_moveCall we see that the last couple of parameters are as follows:

  • gas: ObjectID (not required)
  • gas_budget: BigInt (required)
  • execution_mode (we will ignore this one).

So, if you want to provide an explicit gas coin, let’s say it has an ID of 0x12345679abcdef, and a gas budget of 1000 then you would do it as follows:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "unsafe_moveCall",
  "params": [
    "0x6356f0141b7a3dd839279ded06ba9e55d928d7019eff01ca981aba44e31afa96",
    "0xba153169476e8c3114962261d1edc70de5ad9781b83cc617ecc8c1923191cae0",
    "pair",
    "get_lp_name",
    [],
    [],
    "0x123456789abcdef",
    "1000",
  ]
}

If you did not want to provide an explicit gas coin (in which case the fullnode would choose for you) then you could do it as follows:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "unsafe_moveCall",
  "params": [
    "0x6356f0141b7a3dd839279ded06ba9e55d928d7019eff01ca981aba44e31afa96",
    "0xba153169476e8c3114962261d1edc70de5ad9781b83cc617ecc8c1923191cae0",
    "pair",
    "get_lp_name",
    [],
    [],
    null,
    "1000",
  ]
}
2 Likes

Thanks a lot for your help!

2 Likes