abigen!

abigen! is a procedural macro -- it generates code. It accepts inputs in the format of:

ProgramType(name="MyProgramType", abi="my_program-abi.json")...

where:

  • ProgramType is one of: Contract, Script or Predicate,

  • name is the name that will be given to the generated bindings,

  • abi is either a path to the json abi file or its actual contents.


So, an abigen! which generates bindings for two contracts and one script looks like this:

        abigen!(
            Contract(name="ContractA", abi="packages/fuels/tests/bindings/sharing_types/contract_a/out/debug/contract_a-abi.json"),
            Contract(name="ContractB", abi="packages/fuels/tests/bindings/sharing_types/contract_b/out/debug/contract_b-abi.json"),
            Script(name="MyScript", abi="packages/fuels/tests/scripts/script_with_arguments/out/debug/script_with_arguments-abi.json"),
            Predicate(name="MyPredicate", abi="packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic-abi.json"),
        );

How does the generated code look?

A rough overview:

pub mod abigen_bindings {
    pub mod contract_a_mod {
        struct SomeCustomStruct{/*...*/};
        // other custom types used in the contract
        
        struct ContractA {/*...*/};
        impl ContractA {/*...*/};
        // ...
    }
    pub mod contract_b_mod {
        // ...
    }
    pub mod my_script_mod {
        // ...
    }
    pub mod my_predicate_mod{
        // ...
    }
    pub mod shared_types{
        // ...
    }
}

pub use contract_a_mod::{/*..*/};
pub use contract_b_mod::{/*..*/};
pub use my_predicate_mod::{/*..*/};
pub use shared_types::{/*..*/};

Each ProgramType gets its own mod based on the name given in the abigen!. Inside the respective mods, the custom types used by that program are generated, and the bindings through which the actual calls can be made.

One extra mod called shared_types is generated if abigen! detects that the given programs share types. Instead of each mod regenerating the type for itself, the type is lifted out into the shared_types module, generated only once, and then shared between all programs that use it.

A type is deemed shared if its name and definition match up. This can happen either because you've used the same library (a custom one or a type from the stdlib) or because you've happened to define the exact same type.

Finally, pub use statements are inserted, so you don't have to fully qualify the generated types. To avoid conflict, only types that have unique names will get a pub use statement. If you find rustc can't find your type, it might just be that there is another generated type with the same name. To fix the issue just qualify the path by doing abigen_bindings::whatever_contract_mod::TheType.

Note: It is highly encouraged that you generate all your bindings in one abigen! call. Doing it in this manner will allow type sharing and avoid name collisions you'd normally get when calling abigen! multiple times inside the same namespace. If you choose to proceed otherwise, keep in mind the generated code overview presented above and appropriately separate the abigen! calls into different modules to resolve the collision.

Using the bindings

Let's look at a contract with two methods: initialize_counter(arg: u64) -> u64 and increment_counter(arg: u64) -> u64, with the following JSON ABI:

{
  "types": [
    {
      "typeId": 0,
      "type": "u64",
      "components": null,
      "typeParameters": null
    }
  ],
  "functions": [
    {
      "inputs": [
        {
          "name": "value",
          "type": 0,
          "typeArguments": null
        }
      ],
      "name": "initialize_counter",
      "output": {
        "name": "",
        "type": 0,
        "typeArguments": null
      }
    },
    {
      "inputs": [
        {
          "name": "value",
          "type": 0,
          "typeArguments": null
        }
      ],
      "name": "increment_counter",
      "output": {
        "name": "",
        "type": 0,
        "typeArguments": null
      }
    }
  ]
}

By doing this:

            use fuels::prelude::*;
            // Replace with your own JSON abi path (relative to the root of your crate)
            abigen!(Contract(
                name = "MyContractName",
                abi = "examples/rust_bindings/src/abi.json"
            ));

or this:

            use fuels::prelude::*;
            abigen!(Contract(
                name = "MyContract",
                abi = r#"
            {
                "types": [
                  {
                    "typeId": 0,
                    "type": "u64",
                    "components": null,
                    "typeParameters": null
                  }
                ],
                "functions": [
                  {
                    "inputs": [
                      {
                        "name": "value",
                        "type": 0,
                        "typeArguments": null
                      }
                    ],
                    "name": "initialize_counter",
                    "output": {
                      "name": "",
                      "type": 0,
                      "typeArguments": null
                    }
                  },
                  {
                    "inputs": [
                      {
                        "name": "value",
                        "type": 0,
                        "typeArguments": null
                      }
                    ],
                    "name": "increment_counter",
                    "output": {
                      "name": "",
                      "type": 0,
                      "typeArguments": null
                    }
                  }
                ]
              }
            "#
            ));

you'll generate this (shortened for brevity's sake):

pub mod abigen_bindings {
    pub mod my_contract_name_mod {
        pub struct MyContractName {
            contract_id: Bech32ContractId,
            wallet: WalletUnlocked,
        }
        impl MyContractName {
            pub fn new(contract_id: Bech32ContractId, wallet: WalletUnlocked) -> Self {
                Self {
                    contract_id,
                    wallet,
                }
            }
            pub fn get_contract_id(&self) -> &Bech32ContractId {
                &self.contract_id
            }
            pub fn get_wallet(&self) -> WalletUnlocked {
                self.wallet.clone()
            }
            pub fn with_wallet(&self, mut wallet: WalletUnlocked) -> Result<Self, Error> {
                let provider = self.wallet.get_provider()?;
                wallet.set_provider(provider.clone());
                Ok(Self {
                    contract_id: self.contract_id.clone(),
                    wallet,
                })
            }
            pub async fn get_balances(&self) -> Result<HashMap<String, u64>, Error> {
                self.wallet
                    .get_provider()?
                    .get_contract_balances(&self.contract_id)
                    .await
                    .map_err(Into::into)
            }
            pub fn methods(&self) -> MyContractNameMethods {
                MyContractNameMethods {
                    contract_id: self.contract_id.clone(),
                    wallet: self.wallet.clone(),
                    logs_map: get_logs_hashmap(&[], Some(self.contract_id.clone())),
                }
            }
        }
        pub struct MyContractNameMethods {
            contract_id: Bech32ContractId,
            wallet: WalletUnlocked,
            logs_map: HashMap<(Bech32ContractId, u64), ParamType>,
        }
        impl MyContractNameMethods {
            #[doc = "Calls the contract's `initialize_counter` function"]
            pub fn initialize_counter(&self, value: u64) -> ContractCallHandler<u64> {
                let provider = self.wallet.get_provider().expect("Provider not set up");
                let log_decoder = LogDecoder {
                    logs_map: self.logs_map.clone(),
                };
                Contract::method_hash(
                    provider,
                    self.contract_id.clone(),
                    &self.wallet,
                    resolve_fn_selector(
                        "initialize_counter",
                        &[<u64 as Parameterize>::param_type()],
                    ),
                    &[Tokenizable::into_token(value)],
                    log_decoder,
                )
                .expect("method not found (this should never happen)")
            }
            #[doc = "Calls the contract's `increment_counter` function"]
            pub fn increment_counter(&self, value: u64) -> ContractCallHandler<u64> {
                let provider = self.wallet.get_provider().expect("Provider not set up");
                let log_decoder = LogDecoder {
                    logs_map: self.logs_map.clone(),
                };
                Contract::method_hash(
                    provider,
                    self.contract_id.clone(),
                    &self.wallet,
                    resolve_fn_selector(
                        "increment_counter",
                        &[<u64 as Parameterize>::param_type()],
                    ),
                    &[Tokenizable::into_token(value)],
                    log_decoder,
                )
                .expect("method not found (this should never happen)")
            }
        }
    }
}
pub use abigen_bindings::my_contract_name_mod::MyContractName;
pub use abigen_bindings::my_contract_name_mod::MyContractNameMethods;

Note: that is all generated code. No need to write any of that. Ever. The generated code might look different from one version to another, this is just an example to give you an idea of what it looks like.

Then, you're able to use it to call the actual methods on the deployed contract:

        // This is an instance of your contract which you can use to make calls to your functions
        let contract_instance = MyContract::new(contract_id, wallet);

        let response = contract_instance
            .methods()
            .initialize_counter(42) // Build the ABI call
            .call() // Perform the network call
            .await?;

        assert_eq!(42, response.value);

        let response = contract_instance
            .methods()
            .increment_counter(10)
            .call()
            .await?;

        assert_eq!(52, response.value);