Hi @YKW the dynamic functions are a good challenge.
I don’t know a good solution, but I’ll offer some thoughts that might help.
Declare an ABI
module with the input and output definitions. I’ve left as open objects for now, but would be good if they could be converted to records.
module ABI = {
type input = {"name": string, "type": string}
type output = {"name": string, "type": string}
type def = {
"constant": bool,
"inputs": array<input>,
"name": string,
"outputs": array<output>,
"payable": bool,
"stateMutability": string,
"type": string,
}
}
Declare Provider and Contract modules:
module Provider = {
type t
@module("ethers") external getDefaultProvider: string => t = "getDefaultProvider"
}
module Contract = {
type t
@module("ethers") @new
external makeContract: (string, array<ABI.def>, Provider.t) => Promise.t<t> = "Contract"
}
Consumers of your bindings would declare a custom ABI module, here’s an example:
module MyABI = {
let config: array<ABI.def> = [
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string",
},
],
"payable": false,
"stateMutability": "view",
"type": "function",
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string",
},
],
"payable": false,
"stateMutability": "view",
"type": "function",
},
]
let name: Contract.t => Promise.t<string> = %raw(`
(contract) => contract.name().call()
`)
let symbol: Contract.t => Promise.t<string> = %raw(`
(contract) => contract.symbol().call()
`)
}
Edit: For function with args, something like:
let other: (Contract.t, string, int) => Promise.t<string> = %raw(`
(contract, a, b) => contract.other().call(undefined, a, b)
`)
This provides the ABI configuration and the corresponding functions for that configuration.
I’m seeing this as another kind of binding - writing bindings for the configuration.
The functions themselves are %raw()
mappings. Not sure if there is an easier way considering variable input and output types.
Using all of this would be something like this:
let provider = Provider.getDefaultProvider("https://rpc.gnosischain.com/")
let getContract = (contractAddress, config) => {
Contract.makeContract(contractAddress, config, provider)
}
getContract("0xB5d592f85ab2D955c25720EbE6FF8D4d1E1Be30", MyABI.config)
->Promise.then(contract => {
let name = contract->MyABI.name
let symbol = contract->MyABI.symbol
Promise.all2((name, symbol))->Promise.then(((name, symbol)) => {
Js.log2(name, symbol)
Promise.resolve()
})
})
->ignore
Not a perfect solution, but maybe some ideas that can help?
Edit: It looks like the TypeScript bindings don’t attempt to type the contract functions:
export type ContractFunction<T = any> = (...args: Array<any>) => Promise<T>;
As far as I’m aware, there is no equivalent in ReScript.