api icon indicating copy to clipboard operation
api copied to clipboard

How to decode extrinsic input using api-contract?

Open mkchungs opened this issue 2 years ago • 1 comments

Background: Polkaholic would like to support wasm contract UI + indexing the on-chain contract-call. Is there a way to decode raw extrinsic input using the api-contract package if contract's metadata.json is known?

For example, I've deployed a sample psp22 contract on shiden network (https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frpc.shiden.astar.network#/contracts)

Steps I took:

  • (1) Deploying the PSP22-META contract (contract metadata is published on gist) Extrinsic: https://polkaholic.io/tx/0x2c986a6cb47b94a9e50f5d3f660e0f37177989594eb087bf7309c2e15e2340c8 Deployed Contract Address: Xd87RRvCB7JuF3LUYvhNRwgV9WfhkhUJApj45EP5dcDrSi1

  • (2) psp22 token transfer: Sending 1.23456e-13 WLK (decimals 18) from Z4wzSczja5Va2wAsVJEoiLK9i882FUjWWdDnZDrtPzxYp8z to WwjA4NLgbAKgapEvKf86LNk7gcqSQhsNUTN2AXSc6JPjQf4 Extrinsic: https://polkaholic.io/tx/0xa0c23aa9bbb27e18bf48d7df1b51d83fb8be73ed111bd375b40f2ab02f281554 Encode data from Extrinsic: 0xdb20f9f52c8feeab5bd9a317375e01adb6cb959f1fea78c751936d556fa2e36ede425a4740e2010000000000000000000000000000

Question:

How to use api-contract package to decode contract:call input like 0xdb20f9f52c8feeab5bd9a317375e01adb6cb959f1fea78c751936d556fa2e36ede425a4740e2010000000000000000000000000000 given the metadata/selector?

{
    "args": [
        {
            "label": "to",
            "type": {
                "displayName": [
                    "psp22_external",
                    "TransferInput1"
                ],
                "type": 2
            }
        },
        {
            "label": "value",
            "type": {
                "displayName": [
                    "psp22_external",
                    "TransferInput2"
                ],
                "type": 0
            }
        },
        {
            "label": "data",
            "type": {
                "displayName": [
                    "psp22_external",
                    "TransferInput3"
                ],
                "type": 16
            }
        }
    ],
    "docs": [
        " Transfers `value` amount of tokens from the caller's account to account `to`",
        " with additional `data` in unspecified format.",
        "",
        " On success a `Transfer` event is emitted.",
        "",
        " # Errors",
        "",
        " Returns `InsufficientBalance` error if there are not enough tokens on",
        " the caller's account Balance.",
        "",
        " Returns `ZeroSenderAddress` error if sender's address is zero.",
        "",
        " Returns `ZeroRecipientAddress` error if recipient's address is zero."
    ],
    "label": "PSP22::transfer",
    "mutates": true,
    "payable": false,
    "returnType": {
        "displayName": [
            "psp22_external",
            "TransferOutput"
        ],
        "type": 14
    },
    "selector": "0xdb20f9f5"
}

Code Sample :

const {
    ApiPromise,
    WsProvider,
    Keyring
} = require("@polkadot/api");
const {
    CodePromise,
    ContractPromise
} = require('@polkadot/api-contract');

const fs = require("fs");
    
async function main() {
    let chainID = 22007;
    let endpoint = "wss://shiden.api.onfinality.io/public-ws";

    const provider = new WsProvider(endpoint);
    provider.on('disconnected', () => {
        console.log('CHAIN API DISCONNECTED', chainID);
    });
    provider.on('connected', () => console.log('chain API connected', chainID));
    provider.on('error', (error) => console.log('chain API error', chainID, error));
    //const options = require("@astar-network/astar-api");                                                                                                                                                                                    
    let api = await ApiPromise.create({
        provider: provider
    });

    console.log(`You are connected to ASTAR/SHIDEN chain ${chainID} endpoint=${endpoint} with options`);

    let metadata = JSON.parse(fs.readFileSync("my_psp22/metadata.json"));
    let wasm = fs.readFileSync("my_psp22/my_psp22_metadata.wasm");
    
    // 2. read "val" (should be true) from contract address
    let deployerAddress = "Z4wzSczja5Va2wAsVJEoiLK9i882FUjWWdDnZDrtPzxYp8z"
    let address = "Xd87RRvCB7JuF3LUYvhNRwgV9WfhkhUJApj45EP5dcDrSi1"
    console.log(address, metadata);
    const contract = new ContractPromise(api, metadata, address);
    let q = "psp22::balanceOf"
    console.log(contract.query[q].meta)
    let f = contract.query[q];
    let gasLimit = 3000n * 1000000n;
    let storageDepositLimit = null;
    let from = "WwjA4NLgbAKgapEvKf86LNk7gcqSQhsNUTN2AXSc6JPjQf4";
    const { gasRequired, storageDeposit, result, output } = await f(deployerAddress, { gasLimit: -1, storageDepositLimit },  from);
    if ( result.isOk ) {
	console.log('success', output.toHuman());
    } else {
	console.log('error', result.asErr.toHuman());
    }

    //How to decode contract.call (Z4wzSczja5Va2wAsVJEoiLK9i882FUjWWdDnZDrtPzxYp8z send 1.23456e-13 WLK (decimals 18) to WwjA4NLgbAKgapEvKf86LNk7gcqSQhsNUTN2AXSc6JPjQf4)
    //`0xdb20f9f52c8feeab5bd9a317375e01adb6cb959f1fea78c751936d556fa2e36ede425a4740e2010000000000000000000000000000`

contract.abi.decodeMessage('0xdb20f9f52c8feeab5bd9a317375e01adb6cb959f1fea78c751936d556fa2e36ede425a4740e2010000000000000000000000000000')
/* Error msg:
Uncaught Error: Number can only safely store up to 53 bits
    at assert (/root/node_modules/@polkadot/api-contract/node_modules/bn.js/lib/bn.js:6:21)
    at BN.toNumber (/root/node_modules/@polkadot/api-contract/node_modules/bn.js/lib/bn.js:547:7)
    at compactStripLength (/root/node_modules/@polkadot/api-contract/node_modules/@polkadot/util/cjs/compact/stripLength.js:27:33)
    at Abi.#decodeMessage (/root/node_modules/@polkadot/api-contract/cjs/Abi/index.js:229:54)
    at Abi.decodeMessage (/root/node_modules/@polkadot/api-contract/cjs/Abi/index.js:122:31)
}
*/

main()
    .then(() => process.exit(0))
    .catch((e) => {
        console.error('ERROR', e);
        process.exit(1);
    });

The api-contract doc doesn't show how to decode it

  • Environment:

    • [X] Node.js
    • [ ] Browser
    • [ ] Other (limited support for other environments)
  • Language:

    • [X] JavaScript
    • [ ] TypeScript (include tsc --version)
    • [ ] Other

mkchungs avatar Aug 24 '22 18:08 mkchungs

This issue has been open for 21 days with no activity and is not labelled as an enhancement. It will be closed in 7 days.

polkadot-js-bot avatar Oct 03 '22 05:10 polkadot-js-bot

To decode the message, use the decodeMessage method of the abi parameter of ContractPromise object, eg to decode the transfer contract call https://polkaholic.io/tx/0x02e86275ad0f6129077b557042aca2fea5fad97c02fa75230ad17e08fd2b2eec

let address = "Xd87RRvCB7JuF3LUYvhNRwgV9WfhkhUJApj45EP5dcDrSi1"
const contract = new ContractPromise(api, metadata, address);
let decodedMessage = contract.abi.decodeMessage(compactAddLength(hexToU8a("0xdb20f9f5e6b912626c9dfa3cd9e65b4412b19eb9d123edb1aa22d492a58a88091c483a7a5304000000000000000000000000000020636f6e6772617473")));
decodedMessage.args.map( (a)=> {
        console.log(a.toHuman());
})

which outputs the arguments to your transfer tx:

b9pHTRUj5uJqRTdX5pjixjigLxfEDP16Enu9EbPAot5guhx
1,107
congrats

It was necessary to look inside the api-contracts implementation of decodeMessage to know that it was essential to use compactAddLength and hexToU8a. I imagine a more flexible implementation would accept hex strings maybe?

sourabhniyogi avatar Oct 11 '22 05:10 sourabhniyogi

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue if you think you have a related problem or query.

polkadot-js-bot avatar Oct 18 '22 16:10 polkadot-js-bot