polkadot-js / api

Promise and RxJS APIs around Polkadot and Substrate based chains via RPC calls. It is dynamically generated based on what the Substrate runtime provides in terms of metadata.
Apache License 2.0
1.07k stars 354 forks source link

Question: How to pass argument from typescript that expects 'u32' #4897

Closed Yuripetusko closed 2 years ago

Yuripetusko commented 2 years ago

We have a custom-built pallet that expects one of it's argument as u32, I can not find a way to pass a type that typescript would accept, not sure how to create a u32 type from the client.

In some other pallets I see that u32 is often also paired with AnyNumber when doing type- but not in our case, (it's not a first-level argument, but a nested struct of an argument) https://github.com/rmrk-team/rmrk-substrate-js/blob/master/libs/rmrk-substrate-api/src/interfaces/rmrkEquip/types.ts#L20

I tried new u32 but it expects a whole registry, numberToU8a etc. But typescript complains still

jacogr commented 2 years ago

Ok, so generally it should show an AnyNumber, the reason being that any number input can take one of the following - number, BN, BigInt, hex, decimal string. So where number are required, if you pass any of those, it will convert it to whatever is needed to communicate with the runtime.

So at worst (and I'm not advocating littering the code like this), do `call(123 as any, ...)

The right solution is to ensure the typegen handles this case correctly. For this, the chain (and or static metadata) as well as the actual type to look at would be great to see what can be done to ensure typegen does it the way it is supposed to.

Yuripetusko commented 2 years ago

The types for our pallets are a mess in general :D But it's all WIP still.

For example let's take z field as example

It's set as primitive here first https://github.com/rmrk-team/rmrk-substrate/blob/11662cb0e543b2ed91445b1497abc036a1db4b37/traits/src/lib.rs#L34

Then used on this trait here https://github.com/rmrk-team/rmrk-substrate/blob/11662cb0e543b2ed91445b1497abc036a1db4b37/traits/src/part.rs#L16

And finally accepted as an argument here: https://github.com/rmrk-team/rmrk-substrate/blob/11662cb0e543b2ed91445b1497abc036a1db4b37/pallets/rmrk-equip/src/functions.rs#L79

You can see metadata here https://github.com/rmrk-team/rmrk-substrate-js/blob/master/tools/rmrk-substrate-metadata.json

And type definitions that I am trying to create here: https://github.com/rmrk-team/rmrk-substrate-js/tree/master/libs/rmrk-substrate-api/src/interfaces/rmrkEquip

I am not a rust dev FYI, I am a fullstack dev who is trying to write a UI/scripts to use these pallets through polkadot.js api

jacogr commented 2 years ago

404 for me on the metadata link (I need this to generate against to be able to test)

Yuripetusko commented 2 years ago

404 for me on the metadata link (I need this to generate against to be able to test)

Sorry forgot it's private. here's gist

https://gist.github.com/Yuripetusko/8fec250d828dd92747a73f6ec51d3ec2

jacogr commented 2 years ago

Perfect, ty.

jacogr commented 2 years ago

So the Fixed part is the following -

  /** @name RmrkTraitsPartFixedPart (132) */
  export interface RmrkTraitsPartFixedPart extends Struct {
    readonly id: u32;
    readonly z: u32;
    readonly src: Bytes;
  }

Which is all fine since that is the actual type definition, so it will be "tight", i.e. when you do part.z it will return a u32.

Where your issue lies is the following -

createBase: AugmentedSubmittable<(
  baseType: Bytes | string | Uint8Array,
  symbol: Bytes | string | Uint8Array,
  parts: Vec<RmrkTraitsPartPartType> | (RmrkTraitsPartPartType | { FixedPart: any } | { SlotPart: any } | string | Uint8Array)[]
) => SubmittableExtrinsic<ApiType>, [Bytes, Bytes, Vec<RmrkTraitsPartPartType>]>;

So here the Struct is not expanded, but at the same time it would allow you to pass in anything. So this would be all ok, even for the type-checker -

api.tx.rmrkEquip.createBase(
  // base
  'something',
  // symbol
  'LEET',
  // parts
  [
    // fixed version
    { fixedPart: { id: 1, z: 2, src: 'anything' },
    // slot version
    { slotPart: { id: 3, z: 4, ... } } 
  ]
  ...

So in none of these cases should it make TS complain at all. (Obviously it would be great if the typegen doesn't create any for all these enum definitions and expands them fully... it is one of those code TODOs that is need of elbow grease to get done)

Yuripetusko commented 2 years ago

Thanks I see, I was confused a bit in few places in my code, but additionally I was trying to use types created from definitions to have a separate function that generates Vec<RmrkTraitsPartPartType> before passing it to createBase

I cannot do like createBaseParts: (): RmrkTraitsPartPartType[] => {...} as then the types are not the same as createBase arguments as RmrkTraitsPartPartType will not have the option with any

Yuripetusko commented 2 years ago

I think also if functions arguments were exported as separate types that would help this cause too. For example in createBase last argument would be export type CreateBasePartsArgument = Vec<RmrkTraitsPartPartType> | (RmrkTraitsPartPartType | { FixedPart: any } | { SlotPart: any } | string | Uint8Array)[] that would solve my problem and I could easily use this type in my function.

jacogr commented 2 years ago

TS does allow you to extract parameters for any function. On a transfer (I used something with an enum to show the approach) -

const createTransfer: () => Parameters<typeof api.tx.balances.transfer> => {... };

The signature above, from VSCode, is

const createTransfer: () => [dest: string | MultiAddress | Uint8Array | {
    Id: any;
} | {
    Index: any;
} | {
    Raw: any;
} | {
    Address32: any;
} | {
    Address20: any;
}, value: Compact<u128> | AnyNumber]

Which means I can do -

const createTransfer: () => Parameters<typeof api.tx.balances.transfer> => [{ id: '5F...' }, 123];

Then to create/call -

await api.tx.balances.transfer(...createTransfer());

PS: I did look at what it would take to extract the enum correctly and it is not quite straight-forward, the same applies to structs. It goes slightly haywire with the nested stuff. Not impossible at all, you just need to have your wits with you to get it non-any.

Yuripetusko commented 2 years ago

Thanks, didn't know or forgot about the Parameter utility type. That will do for now for my case, but yeah, would be great to hear a more strict generated types here sometimes in future if possible

polkadot-js-bot commented 2 years ago

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.