Caver-js tuple 타입의 데이터 배열을 encoding하여 contract에 전송하는 법이 있을까요?

caver js를 사용하여 컨트렉트에 데이터값을 전달하려고 하는데 잘 이해가 되지 않는게 있습니다.

const abi = [
{
“constant”: false,
“inputs”: [
{
“name”: “_id”,
“type”: “uint256”
},
{
“components”: [
{
“name”: “trait_type”,
“type”: “string”
},
{
“name”: “display_type”,
“type”: “string”
},
{
“name”: “value”,
“type”: “uint8”
}
],
“name”: “_attr”,
“type”: “tuple[]”
}
],
“name”: “pushAttribute”,
“outputs”: [],
“payable”: false,
“stateMutability”: “nonpayable”,
“type”: “function”
}
]

이게 제 abi중 일부인데
input 데이터중에 components라고 [stirng, string, uint8] 이라는 tuple 타입의 배열을 입력받습니다.
여기서 caver의 기능중 하나인 encodeParameters 함수를 통해 해당 내용을 contract에 전송 가능한 데이터로
인코딩해서 전송하력 하는데 문제가 발생하였습니다.

types.forEach(function(type) {
^

TypeError: types.forEach is not a function

아무래도 caver-js에서 foreach로 배열을 인코딩하는 과정에서 문제가 생기는거 같은데
tuple 타입의 배열을 encoding 하는 방법이 있을까요??

2줄 요약
1.구조체 배열을 encoding 하는 방법이 있을까요?
2. 감사합니다.

안녕하세요. 질문 올려주셔서 감사합니다.
tuple 배열 인코딩 방법은 아래의 예제를 참고해 주시기 바랍니다.

const encoded1 = caver.abi.encodeParameters(
        [
            {
                name: '_id',
                type: 'uint256',
            },
            {
                components: [
                    {
                        name: 'trait_type',
                        type: 'string',
                    },
                    {
                        name: 'display_type',
                        type: 'string',
                    },
                    {
                        name: 'value',
                        type: 'uint8',
                    },
                ],
                name: '_attr',
                type: 'tuple[]',
            },
        ],
        [1, [['traitType', 'displayType', 1], ['traitType2', 'displayType2', 2]]]
    )

    const encoded2 = caver.abi.encodeParameters(
        ['uint256', 'tuple(string,string,uint8)[]'],
        [1, [['traitType', 'displayType', 1], ['traitType2', 'displayType2', 2]]]
    )

위와 같이 2가지 방법을 사용하실 수 있습니다.

1 Like

해당 내용을 적용하게 되면

param.map is not a function

이라는 에러가 발생합니다.

at modifyParams (/Users/rimose/Desktop/projects/node_modules/caver-js/packages/caver-abi/src/index.js:126:34)

여기서 문제가 발생한다고하여 찾아본 결과

return param.map(p => {
// coder.type.replace('[]','') can handle’tuple(string,string)[]’, but cannot handle `tuple(string,string)[3]’.
// Therefore, in order to handle tuple arrays of fixed length, the logic is changed to handle strings using regular expression expressions.
const replacedType = coder.type.replace(/[[1-9]*]/g, ‘’)
const parameterType = ParamType.from(replacedType)
const gotCoder = ethersAbiCoder._getCoder(parameterType)
modifyParams(gotCoder, p)
})

해당 내용의 param이 encodeParameters 의 내용중에서

param = self.formatParam(type, param)

이렇게 정의 되는 과정중에 제대로 선언되지 않는것 같습니다.

실행하신 코드를 공유해 주실 수 있나요?
제가 위에 공유드린 코드는 에러가 재현되지 않아서요.

1 Like
const { arrayify } = require('@ethersproject/bytes');
const Caver = require('caver-js');
const address = '0x2D526C7698231f70D7b4b2dD485eB2d280B54DbA'
const abi = [
	{
		"constant": false,
		"inputs": [
			{
				"name": "_id",
				"type": "uint256"
			},
			{
				"components": [
					{
						"name": "trait_type",
						"type": "string"
					},
					{
						"name": "display_type",
						"type": "string"
					},
					{
						"name": "value",
						"type": "uint8"
					}
				],
				"name": "_attr",
				"type": "tuple[]"
			}
		],
		"name": "pushAttribute",
		"outputs": [],
		"payable": false,
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"constant": false,
		"inputs": [
			{
				"name": "_id",
				"type": "uint256"
			}
		],
		"name": "test",
		"outputs": [],
		"payable": false,
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"payable": false,
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"constant": true,
		"inputs": [
			{
				"name": "",
				"type": "uint256"
			}
		],
		"name": "_metas",
		"outputs": [
			{
				"name": "description",
				"type": "string"
			},
			{
				"name": "external_url",
				"type": "string"
			},
			{
				"name": "image",
				"type": "string"
			},
			{
				"name": "name",
				"type": "string"
			}
		],
		"payable": false,
		"stateMutability": "view",
		"type": "function"
	},
	{
		"constant": true,
		"inputs": [
			{
				"name": "_id",
				"type": "uint256"
			}
		],
		"name": "gettest",
		"outputs": [
			{
				"name": "_display_types",
				"type": "string"
			},
			{
				"name": "_trait_type",
				"type": "string"
			},
			{
				"name": "_value",
				"type": "uint8"
			}
		],
		"payable": false,
		"stateMutability": "view",
		"type": "function"
	}
]

const caver = new Caver('https://kaikas.baobab.klaytn.net:8651/');


// caver.klay.getBalance('0x92799E989a144d110E1f24aB20cdCef1dcE03493').then((res)=>{
//     console.log(res);
// })

const Contract = new caver.klay.Contract(abi,address)

const test = [
    {
        trait_type : "trait_type1",
        display_type : "display_type1",
        value : '100'
    }
    // ,
    // {
    //     trait_type : "trait_type2",
    //     display_type : "display_type2",
    //     value : '200'
    // }
];

// const encoded = caver.abi.encodeParameters(
// 	['uint256', 'tuple(string,string,uint8)[]'],
// 	[1, [['traitType', 'displayType', 1], ['traitType2', 'displayType2', 2]]]
// )

const encoded = caver.abi.encodeParameters(
	[
		{
			name: '_id',
			type: 'uint256',
		},
		{
			components: [
				{
					name: 'trait_type',
					type: 'string',
				},
				{
					name: 'display_type',
					type: 'string',
				},
				{
					name: 'value',
					type: 'uint8',
				},
			],
			name: '_attr',
			type: 'tuple[]',
		},
	],
	[1, [['traitType', 'displayType', 1], ['traitType2', 'displayType2', 2]]]
)


Contract.methods.pushAttribute('0',encoded).call().then((res)=>{
console.log(res)
})

caver-js 버전은 1.6.4 입니다.

먼저 caver.klay.Contract 대신 caver.contract를 사용해 주시기 바랍니다.

caver.abi를 사용하고 계신데, 이게 common architecture 이후에 제공되는 패키지라서, 호환성을 위해 caver.contract를 사용해 주셔야 합니다.

일단 보내주신 코드에서 보면 컨트랙트의 함수를 실행하고 계신데요, contract 객체를 통해서 스마트컨트랙트의 함수를 호출하는 경우에는 내부 코드에서 인코딩을 진행하게 됩니다. 그러므로 따로 encoding된 스트링을 넘길 필요가 없습니다.

그리고 call의 경우 스마트컨트랙트의 상태를 변경하지 않는 단순한 값읽기의 경우에 사용되는 함수인데, pushAttribute는 스마트컨트랙트의 상태를 변경하는 함수이기 때문에 send를 호출해야 합니다.
send 는 실제로 SmartContractExecution 트랜잭션을 Klaytn으로 전송하기 때문에 트랜잭션을 생성하는 데에 필요한 정보 (from, gas)를 정의해 주어야 합니다.
아래 예제를 참고해 주세요. 위에 정의된 abi와 address는 생략했습니다.

const contract = new caver.contract(abi, address)

// caver.contract가 트랜잭션을 전송할 때 사용하는 계정은 `caver.wallet`에 추가되어 있어야 함.
const keyring = caver.wallet.add(
    caver.wallet.keyring.createFromPrivateKey('0x{private key}')
)
contract.methods
    .pushAttribute(1, [['traitType', 'displayType', 1], ['traitType2', 'displayType2', 2]])
    .send({ from: keyring.address, gas: 1000000 })
    .then(res => {
        console.log(res)
    })
1 Like

답변이 되었습니다.
감사합니다.
공식문서를 보고 이것저것 만들어보고는 있다보니
개념적으로 잘못알고 있거나 명확하지 않은 부분이 많아서 더욱 헤메이다
이렇게 글을 남기게 되었는데
올해들어 가장 잘한 선택인거같아 기분이 좋네요 ㅎㅎ
좋은 하루 보내세요!

1 Like

네넵 ^^ 감사합니다. 편하게 질문 남겨주시면 답변 드리도록 하겠습니다 :slight_smile:

1 Like