diff --git a/.gitignore b/.gitignore
index 7b093f9e1a3..77521831c5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@ swift/wallet-core/
 codegen-v2/bindings/
 
 src/Generated/*.cpp
+include/Generated/TrustWalletCore/*.h
 include/TrustWalletCore/TWHRP.h
 include/TrustWalletCore/TW*Proto.h
 include/TrustWalletCore/TWEthereumChainID.h
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6fec5513326..63c1444f57b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -111,6 +111,7 @@ endif ()
 target_include_directories(TrustWalletCore
         PUBLIC
         $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/Generated>
         $<INSTALL_INTERFACE:include>
         PRIVATE
         ${CMAKE_CURRENT_SOURCE_DIR}/src
diff --git a/codegen-v2/Cargo.lock b/codegen-v2/Cargo.lock
index 6ed11b1c48a..098922255d8 100644
--- a/codegen-v2/Cargo.lock
+++ b/codegen-v2/Cargo.lock
@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "aho-corasick"
@@ -35,6 +35,7 @@ dependencies = [
  "handlebars",
  "heck",
  "pathdiff",
+ "regex",
  "serde",
  "serde_json",
  "serde_yaml",
@@ -214,22 +215,51 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.79"
+version = "1.0.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.35"
+version = "1.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
 dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
 [[package]]
 name = "ryu"
 version = "1.0.17"
@@ -293,9 +323,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.52"
+version = "2.0.96"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
+checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/codegen-v2/Cargo.toml b/codegen-v2/Cargo.toml
index 43c2afea1c6..b1692860f20 100644
--- a/codegen-v2/Cargo.toml
+++ b/codegen-v2/Cargo.toml
@@ -21,3 +21,4 @@ serde_yaml = "0.9.21"
 toml_edit = "0.21.0"
 handlebars = "4.3.6"
 heck = "0.4.1"
+regex = "1.11.1"
diff --git a/codegen-v2/src/codegen/cpp/code_gen.rs b/codegen-v2/src/codegen/cpp/code_gen.rs
new file mode 100644
index 00000000000..a73f1d89a9e
--- /dev/null
+++ b/codegen-v2/src/codegen/cpp/code_gen.rs
@@ -0,0 +1,302 @@
+use regex::Regex;
+use serde::{Deserialize, Serialize};
+use std::fmt::Write as _;
+use std::fs;
+use std::io::Write;
+
+use crate::Error::BadFormat;
+use crate::Result;
+
+static IN_DIR: &str = "../rust/bindings/";
+static HEADER_OUT_DIR: &str = "../include/Generated/TrustWalletCore/";
+static SOURCE_OUT_DIR: &str = "../src/Generated/";
+
+#[derive(Deserialize, Serialize, Debug)]
+pub struct TWConfig {
+    pub class: String,
+    pub static_functions: Vec<TWStaticFunction>,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+pub struct TWStaticFunction {
+    pub name: String,
+    pub rust_name: String,
+    pub args: Vec<TWArg>,
+    pub return_type: String,
+    pub docs: Vec<String>,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+pub struct TWArg {
+    pub name: String,
+    pub ty: String,
+}
+
+fn convert_rust_type_to_cpp(ty: &str) -> String {
+    let trimmed = ty.replace(" ", "");
+    if let Some(captures) = Regex::new(r"^Nonnull<(.+)>$")
+        .expect("Failed to create regex")
+        .captures(&trimmed)
+    {
+        format!("{} *_Nonnull", &captures[1])
+    } else if let Some(captures) = Regex::new(r"^NonnullMut<(.+)>$")
+        .expect("Failed to create regex")
+        .captures(&trimmed)
+    {
+        format!("{} *_Nonnull", &captures[1])
+    } else if let Some(captures) = Regex::new(r"^Nullable<(.+)>$")
+        .expect("Failed to create regex")
+        .captures(&trimmed)
+    {
+        format!("{} *_Nullable", &captures[1])
+    } else if let Some(captures) = Regex::new(r"^NullableMut<(.+)>$")
+        .expect("Failed to create regex")
+        .captures(&trimmed)
+    {
+        format!("{} *_Nullable", &captures[1])
+    } else {
+        ty.to_string()
+    }
+}
+
+fn generate_license(file: &mut std::fs::File) -> Result<()> {
+    writeln!(file, "// SPDX-License-Identifier: Apache-2.0")?;
+    writeln!(file, "//")?;
+    writeln!(file, "// Copyright © 2017 Trust Wallet.\n")?;
+    Ok(())
+}
+
+fn generate_header_guard(file: &mut std::fs::File) -> Result<()> {
+    writeln!(file, "#pragma once\n")?;
+    Ok(())
+}
+
+fn generate_header_includes(file: &mut std::fs::File, info: &TWConfig) -> Result<()> {
+    writeln!(file, "#include <TrustWalletCore/TWBase.h>")?;
+
+    // Include headers based on argument types
+    let mut included_headers = std::collections::HashSet::new();
+    for func in &info.static_functions {
+        for arg in &func.args {
+            if arg.ty.contains("TWString") && included_headers.insert("TWString.h") {
+                writeln!(file, "#include <TrustWalletCore/TWString.h>")?;
+            }
+            // Additional type checks can be added here in the future
+        }
+    }
+
+    Ok(())
+}
+
+fn generate_class_declaration(file: &mut std::fs::File, info: &TWConfig) -> Result<()> {
+    writeln!(file, "TW_EXPORT_CLASS\nstruct {};\n", info.class)?;
+    Ok(())
+}
+
+fn generate_function_signature(
+    class_name: &str,
+    func: &TWStaticFunction,
+    is_declaration: bool,
+) -> Result<String> {
+    let return_type = convert_rust_type_to_cpp(&func.return_type);
+    let whether_export = if is_declaration {
+        "TW_EXPORT_STATIC_METHOD "
+    } else {
+        ""
+    };
+    let mut signature = format!("{whether_export}{return_type} {class_name}{}", func.name);
+    signature += "(";
+    for (i, arg) in func.args.iter().enumerate() {
+        write!(
+            &mut signature,
+            "{} {}",
+            convert_rust_type_to_cpp(&arg.ty),
+            arg.name
+        )
+        .map_err(|e| BadFormat(e.to_string()))?;
+        if i < func.args.len() - 1 {
+            signature += ", ";
+        }
+    }
+    signature += ")";
+    Ok(signature)
+}
+
+fn generate_function_declaration(
+    file: &mut std::fs::File,
+    class_name: &str,
+    func: &TWStaticFunction,
+) -> Result<()> {
+    let func_dec = generate_function_signature(class_name, func, true)?;
+    for doc in &func.docs {
+        writeln!(file, "/// {}", doc)?;
+    }
+    writeln!(file, "{func_dec};\n")?;
+    Ok(())
+}
+
+pub fn generate_header(info: &TWConfig) -> Result<()> {
+    let file_path = format!("{HEADER_OUT_DIR}/{}.h", info.class);
+    let mut file = std::fs::File::create(&file_path)?;
+
+    generate_license(&mut file)?;
+    generate_header_guard(&mut file)?;
+    generate_header_includes(&mut file, info)?;
+
+    writeln!(file, "\nTW_EXTERN_C_BEGIN\n")?;
+
+    generate_class_declaration(&mut file, info)?;
+    for func in &info.static_functions {
+        generate_function_declaration(&mut file, &info.class, func)?;
+    }
+
+    writeln!(file, "TW_EXTERN_C_END")?;
+
+    file.flush()?;
+
+    Ok(())
+}
+
+fn generate_source_includes(file: &mut std::fs::File, info: &TWConfig) -> Result<()> {
+    writeln!(
+        file,
+        "#include <Generated/TrustWalletCore/{}.h>",
+        info.class
+    )?;
+    writeln!(file, "#include \"rust/Wrapper.h\"")?;
+    Ok(())
+}
+
+fn generate_function_call(args: &Vec<String>) -> Result<String> {
+    let mut func_call = "(".to_string();
+    for (i, arg) in args.iter().enumerate() {
+        write!(&mut func_call, "{arg}").map_err(|e| BadFormat(e.to_string()))?;
+        if i < args.len() - 1 {
+            func_call += ", ";
+        }
+    }
+    func_call += ");\n";
+    Ok(func_call)
+}
+
+fn generate_return_type(func: &TWStaticFunction, converted_args: &Vec<String>) -> Result<String> {
+    let mut return_string = String::new();
+    match func.return_type.replace(" ", "").as_str() {
+        "NullableMut<TWString>" | "Nullable<TWString>" => {
+            write!(
+                &mut return_string,
+                "    const Rust::TWStringWrapper result = Rust::{}",
+                func.rust_name
+            )
+            .map_err(|e| BadFormat(e.to_string()))?;
+            return_string += generate_function_call(&converted_args)?.as_str();
+            writeln!(&mut return_string, "    if (!result) {{ return nullptr; }}")
+                .map_err(|e| BadFormat(e.to_string()))?;
+            writeln!(
+                &mut return_string,
+                "    return TWStringCreateWithUTF8Bytes(result.c_str());"
+            )
+            .map_err(|e| BadFormat(e.to_string()))?;
+        }
+        _ => {
+            writeln!(&mut return_string, "    return Rust::{}", func.rust_name)
+                .map_err(|e| BadFormat(e.to_string()))?;
+            return_string += generate_function_call(&converted_args)?.as_str();
+        }
+    }
+    Ok(return_string)
+}
+
+fn generate_conversion_code_with_var_name(ty: &str, name: &str) -> Result<(String, String)> {
+    match ty {
+        "TWString *_Nonnull" => {
+            let mut conversion_code = String::new();
+            writeln!(
+                &mut conversion_code,
+                "\tauto& {name}String = *reinterpret_cast<const std::string*>({name});\n\
+                \tconst Rust::TWStringWrapper {name}RustStr = {name}String;"
+            )
+            .map_err(|e| BadFormat(e.to_string()))?;
+            Ok((conversion_code, format!("{}RustStr.get()", name)))
+        }
+        "TWString *_Nullable" => {
+            let mut conversion_code = String::new();
+            writeln!(
+                &mut conversion_code,
+                "\tconst TW::Rust::TWString* {name}Ptr;\n\
+                \tif ({name} != nullptr) {{\n\
+                    \t\tauto& {name}String = *reinterpret_cast<const std::string*>({name});\n\
+                    \t\tconst Rust::TWStringWrapper {name}RustStr = {name}String;\n\
+                    \t\t{name}Ptr = {name}RustStr.get();\n\
+                \t}} else {{\n\
+                    \t\t{name}Ptr = nullptr;\n\
+                \t}}"
+            )
+            .map_err(|e| BadFormat(e.to_string()))?;
+            Ok((conversion_code, format!("{}Ptr", name)))
+        }
+        _ => Ok(("".to_string(), name.to_string())),
+    }
+}
+
+fn generate_function_definition(
+    file: &mut std::fs::File,
+    info: &TWConfig,
+    func: &TWStaticFunction,
+) -> Result<()> {
+    let mut func_def = generate_function_signature(&info.class, func, false)?;
+    func_def += " {\n";
+    let mut converted_args = vec![];
+    for arg in func.args.iter() {
+        let func_type = convert_rust_type_to_cpp(&arg.ty);
+        let (conversion_code, converted_arg) =
+            generate_conversion_code_with_var_name(&func_type, &arg.name)?;
+        func_def += conversion_code.as_str();
+        converted_args.push(converted_arg);
+    }
+    let return_string = generate_return_type(func, &converted_args)?;
+    func_def += return_string.as_str();
+    func_def += "}\n";
+    writeln!(file, "{}", func_def)?;
+    Ok(())
+}
+
+fn generate_source(info: &TWConfig) -> Result<()> {
+    let file_path = format!("{SOURCE_OUT_DIR}/{}.cpp", info.class);
+    let mut file = std::fs::File::create(&file_path)?;
+
+    generate_license(&mut file)?;
+    generate_source_includes(&mut file, info)?;
+
+    writeln!(file, "\nusing namespace TW;\n")?;
+
+    for func in &info.static_functions {
+        generate_function_definition(&mut file, info, func)?;
+    }
+
+    file.flush()?;
+
+    Ok(())
+}
+
+pub fn generate_cpp_bindings() -> Result<()> {
+    std::fs::create_dir_all(HEADER_OUT_DIR)?;
+    std::fs::create_dir_all(SOURCE_OUT_DIR)?;
+
+    let entries = fs::read_dir(IN_DIR)?;
+    for entry in entries {
+        let file_path = entry?.path();
+        if file_path.is_dir() {
+            println!("Found unexpected directory: {}", file_path.display());
+            continue;
+        }
+
+        let file_contents = fs::read_to_string(&file_path)?;
+        let info: TWConfig =
+            serde_yaml::from_str(&file_contents).expect("Failed to parse YAML file");
+
+        generate_header(&info)?;
+        generate_source(&info)?;
+    }
+    Ok(())
+}
diff --git a/codegen-v2/src/codegen/cpp/mod.rs b/codegen-v2/src/codegen/cpp/mod.rs
index 1ef7188d86f..147960dd30c 100644
--- a/codegen-v2/src/codegen/cpp/mod.rs
+++ b/codegen-v2/src/codegen/cpp/mod.rs
@@ -7,6 +7,7 @@ use std::env;
 use std::path::PathBuf;
 
 pub mod blockchain_dispatcher_generator;
+pub mod code_gen;
 pub mod entry_generator;
 pub mod new_blockchain;
 pub mod new_cosmos_chain;
diff --git a/codegen-v2/src/codegen/template_generator.rs b/codegen-v2/src/codegen/template_generator.rs
index 7b725ba74e7..6fae696716d 100644
--- a/codegen-v2/src/codegen/template_generator.rs
+++ b/codegen-v2/src/codegen/template_generator.rs
@@ -38,7 +38,14 @@ impl TemplateGenerator {
             .add_pattern("{TW_CRATE_NAME}", coin.id.to_tw_crate_name())
             .add_pattern("{COIN_ID}", coin.id.as_str())
             .add_pattern("{COIN_TYPE}", coin.coin_type())
-            .add_pattern("{COIN_NAME}", if coin.display_name.len() > 0 { &coin.display_name } else { &coin.name })
+            .add_pattern(
+                "{COIN_NAME}",
+                if coin.display_name.len() > 0 {
+                    &coin.display_name
+                } else {
+                    &coin.name
+                },
+            )
             .add_pattern("{SYMBOL}", &coin.symbol)
             .add_pattern("{DECIMALS}", coin.decimals)
             .add_pattern("{P2PKH_PREFIX}", coin.p2pkh_prefix)
diff --git a/codegen-v2/src/main.rs b/codegen-v2/src/main.rs
index 53fd140cc65..f420701bd6c 100644
--- a/codegen-v2/src/main.rs
+++ b/codegen-v2/src/main.rs
@@ -2,6 +2,7 @@
 //
 // Copyright © 2017 Trust Wallet.
 
+use libparser::codegen::cpp::code_gen::generate_cpp_bindings;
 use libparser::codegen::swift::RenderIntput;
 use libparser::codegen::{cpp, proto, rust};
 use libparser::coin_id::CoinId;
@@ -23,6 +24,7 @@ fn main() -> Result<()> {
         "new-evmchain" => new_evmchain(&args[2..]),
         "new-cosmos-chain" => new_cosmos_chain(&args[2..]),
         "swift" => generate_swift_bindings(),
+        "cpp" => generate_cpp_bindings(),
         _ => Err(Error::InvalidCommand),
     }
 }
diff --git a/codegen/bin/codegen b/codegen/bin/codegen
index 5cd99179bb8..1438a5e73a2 100755
--- a/codegen/bin/codegen
+++ b/codegen/bin/codegen
@@ -15,6 +15,7 @@ Encoding.default_internal = Encoding::UTF_8
 options = OpenStruct.new
 # default input / output path
 options.input = "#{CurrentDir}/../../include/TrustWalletCore"
+options.input_generated = "#{CurrentDir}/../../include/Generated/TrustWalletCore"
 options.output = "#{CurrentDir}/../../"
 options.swift = true
 options.java = true
@@ -62,14 +63,17 @@ end.parse!
 
 entities = []
 files = []
-Dir.foreach(options.input) do |item|
-  next if ['.', '..', '.DS_Store'].include?(item)
+[options.input, options.input_generated].each do |input_dir|
+  Dir.foreach(input_dir) do |item|
+    next if ['.', '..', '.DS_Store'].include?(item)
 
-  entity = Parser.new(path: File.expand_path(File.join(options.input, item))).parse
-  next if entity.nil?
+    file_path = File.expand_path(File.join(input_dir, item))
+    entity = Parser.new(path: file_path).parse
+    next if entity.nil?
 
-  entities << entity
-  files << File.basename(item, '.h').sub(/^TW/, '')
+    entities << entity
+    files << File.basename(item, '.h').sub(/^TW/, '')
+  end
 end
 
 generator = CodeGenerator.new(entities: entities, files: files, output_folder: options.output)
diff --git a/include/TrustWalletCore/TWTONAddressConverter.h b/include/TrustWalletCore/TWTONAddressConverter.h
deleted file mode 100644
index 39bb4dfed7e..00000000000
--- a/include/TrustWalletCore/TWTONAddressConverter.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-//
-// Copyright © 2017 Trust Wallet.
-
-#pragma once
-
-#include "TWBase.h"
-#include "TWString.h"
-
-TW_EXTERN_C_BEGIN
-
-/// TON address operations.
-TW_EXPORT_CLASS
-struct TWTONAddressConverter;
-
-/// Converts a TON user address into a Bag of Cells (BoC) with a single root Cell.
-/// The function is mostly used to request a Jetton user address via `get_wallet_address` RPC.
-/// https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user
-///
-/// \param address Address to be converted into a Bag Of Cells (BoC).
-/// \return Pointer to a base64 encoded Bag Of Cells (BoC). Null if invalid address provided.
-TW_EXPORT_STATIC_METHOD
-TWString *_Nullable TWTONAddressConverterToBoc(TWString *_Nonnull address);
-
-/// Parses a TON address from a Bag of Cells (BoC) with a single root Cell.
-/// The function is mostly used to parse a Jetton user address received on `get_wallet_address` RPC.
-/// https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user
-///
-/// \param boc Base64 encoded Bag Of Cells (BoC).
-/// \return Pointer to a Jetton address.
-TW_EXPORT_STATIC_METHOD
-TWString *_Nullable TWTONAddressConverterFromBoc(TWString *_Nonnull boc);
-
-/// Converts any TON address format to user friendly with the given parameters.
-///
-/// \param address raw or user-friendly address to be converted.
-/// \param bounceable whether the result address should be bounceable.
-/// \param testnet whether the result address should be testnet.
-/// \return user-friendly address str.
-TW_EXPORT_STATIC_METHOD
-TWString *_Nullable TWTONAddressConverterToUserFriendly(TWString *_Nonnull address, bool bounceable, bool testnet);
-
-TW_EXTERN_C_END
diff --git a/kotlin/wallet-core-kotlin/build.gradle.kts b/kotlin/wallet-core-kotlin/build.gradle.kts
index 8fc8be0a32f..b04858f3aa1 100644
--- a/kotlin/wallet-core-kotlin/build.gradle.kts
+++ b/kotlin/wallet-core-kotlin/build.gradle.kts
@@ -100,7 +100,13 @@ kotlin {
             val main by compilations.getting
             main.cinterops.create("WalletCore") {
                 packageName = "com.trustwallet.core"
-                headers(rootDir.parentFile.resolve("include/TrustWalletCore").listFiles()!!)
+                includeDirs(
+                    rootDir.parentFile.resolve("include"),
+                    rootDir.parentFile.resolve("include/TrustWalletCore"),
+                    rootDir.parentFile.resolve("include/Generated/TrustWalletCore")
+                )
+                headers(rootDir.parentFile.resolve("include/Generated/TrustWalletCore").listFiles()!! +
+                       rootDir.parentFile.resolve("include/TrustWalletCore").listFiles()!!)
             }
         }
     }
diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml
new file mode 100644
index 00000000000..47684146c24
--- /dev/null
+++ b/rust/.cargo/config.toml
@@ -0,0 +1,2 @@
+[env]
+CARGO_WORKSPACE_DIR = { value = "", relative = true }
diff --git a/rust/.gitignore b/rust/.gitignore
index bca6f9b85aa..d963b2c89d3 100644
--- a/rust/.gitignore
+++ b/rust/.gitignore
@@ -5,3 +5,6 @@ target/
 
 # These are backup files generated by rustfmt
 **/*.rs.bk
+
+# Generated by tw_macros
+bindings/
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index a1c8d016f1d..cdecec6380b 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "aead"
@@ -314,7 +314,7 @@ dependencies = [
  "proc-macro-crate 3.1.0",
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.96",
  "syn_derive",
 ]
 
@@ -531,7 +531,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.96",
 ]
 
 [[package]]
@@ -567,6 +567,17 @@ dependencies = [
  "syn 1.0.107",
 ]
 
+[[package]]
+name = "derive-syn-parse"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
 [[package]]
 name = "derive_arbitrary"
 version = "1.3.0"
@@ -750,9 +761,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
 
 [[package]]
 name = "hashbrown"
-version = "0.14.0"
+version = "0.15.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
 
 [[package]]
 name = "heck"
@@ -839,9 +850,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.0.0"
+version = "2.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
+checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
 dependencies = [
  "equivalent",
  "hashbrown",
@@ -1199,9 +1210,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.67"
+version = "1.0.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
 dependencies = [
  "unicode-ident",
 ]
@@ -1223,9 +1234,9 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.33"
+version = "1.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
 dependencies = [
  "proc-macro2",
 ]
@@ -1411,9 +1422,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
 
 [[package]]
 name = "serde"
-version = "1.0.189"
+version = "1.0.217"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
 dependencies = [
  "serde_derive",
 ]
@@ -1429,13 +1440,13 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.189"
+version = "1.0.217"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.96",
 ]
 
 [[package]]
@@ -1457,7 +1468,20 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.34+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
 ]
 
 [[package]]
@@ -1540,7 +1564,7 @@ checksum = "e6dc88f1f470d9de1001ffbb90d2344c9dd1a615f5467daf0574e2975dfd9ebd"
 dependencies = [
  "starknet-curve",
  "starknet-ff",
- "syn 2.0.37",
+ "syn 2.0.96",
 ]
 
 [[package]]
@@ -1594,7 +1618,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.37",
+ "syn 2.0.96",
 ]
 
 [[package]]
@@ -1616,9 +1640,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.37"
+version = "2.0.96"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
+checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1634,7 +1658,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.96",
 ]
 
 [[package]]
@@ -2036,6 +2060,18 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "tw_macros"
+version = "0.1.0"
+dependencies = [
+ "derive-syn-parse",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_yaml",
+ "syn 2.0.96",
+]
+
 [[package]]
 name = "tw_memory"
 version = "0.1.0"
@@ -2182,6 +2218,7 @@ name = "tw_substrate"
 version = "0.1.0"
 dependencies = [
  "tw_coin_entry",
+ "tw_encoding",
  "tw_hash",
  "tw_keypair",
  "tw_memory",
@@ -2204,6 +2241,7 @@ dependencies = [
  "tw_hash",
  "tw_keypair",
  "tw_memory",
+ "tw_misc",
  "tw_proto",
 ]
 
@@ -2339,6 +2377,12 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
+
 [[package]]
 name = "uuid"
 version = "1.7.0"
@@ -2372,6 +2416,7 @@ dependencies = [
  "tw_ethereum",
  "tw_hash",
  "tw_keypair",
+ "tw_macros",
  "tw_memory",
  "tw_misc",
  "tw_number",
@@ -2522,5 +2567,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.96",
 ]
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 75b9d7761ab..6449c190126 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -31,6 +31,7 @@ members = [
     "tw_evm",
     "tw_hash",
     "tw_keypair",
+    "tw_macros",
     "tw_memory",
     "tw_misc",
     "tw_number",
diff --git a/rust/tw_macros/Cargo.toml b/rust/tw_macros/Cargo.toml
new file mode 100644
index 00000000000..8a0343c15be
--- /dev/null
+++ b/rust/tw_macros/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "tw_macros"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+derive-syn-parse = "0.2.0"
+proc-macro2 = "1.0.93"
+quote = "1.0.38"
+serde = { version = "1.0", features = ["derive"] }
+serde_yaml = "0.9.21"
+syn = { version = "2.0.96", features = ["full"] }
diff --git a/rust/tw_macros/src/code_gen.rs b/rust/tw_macros/src/code_gen.rs
new file mode 100644
index 00000000000..3b311486b9b
--- /dev/null
+++ b/rust/tw_macros/src/code_gen.rs
@@ -0,0 +1,22 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Debug)]
+pub struct TWConfig {
+    pub class: String,
+    pub static_functions: Vec<TWStaticFunction>,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+pub struct TWStaticFunction {
+    pub name: String,
+    pub rust_name: String,
+    pub args: Vec<TWArg>,
+    pub return_type: String,
+    pub docs: Vec<String>,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+pub struct TWArg {
+    pub name: String,
+    pub ty: String,
+}
diff --git a/rust/tw_macros/src/lib.rs b/rust/tw_macros/src/lib.rs
new file mode 100644
index 00000000000..8c0cd9533ed
--- /dev/null
+++ b/rust/tw_macros/src/lib.rs
@@ -0,0 +1,12 @@
+use proc_macro::TokenStream;
+
+mod code_gen;
+mod tw_ffi;
+
+#[proc_macro_attribute]
+pub fn tw_ffi(attr: TokenStream, item: TokenStream) -> TokenStream {
+    match tw_ffi::tw_ffi(attr.into(), item.into()) {
+        Ok(item) => item.into(),
+        Err(e) => e.to_compile_error().into(),
+    }
+}
diff --git a/rust/tw_macros/src/tw_ffi.rs b/rust/tw_macros/src/tw_ffi.rs
new file mode 100644
index 00000000000..fcf1c804f91
--- /dev/null
+++ b/rust/tw_macros/src/tw_ffi.rs
@@ -0,0 +1,179 @@
+use derive_syn_parse::Parse;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use syn::{
+    parse::{Parse, ParseStream},
+    parse2, Ident, Result, Token,
+};
+
+use std::env;
+use std::fmt;
+use std::fs;
+use std::path::Path;
+
+use crate::code_gen::{TWArg, TWConfig, TWStaticFunction};
+
+pub mod keywords {
+    use syn::custom_keyword;
+
+    custom_keyword!(ty);
+    custom_keyword!(class);
+    custom_keyword!(name);
+    custom_keyword!(static_function);
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum TWFFIType {
+    StaticFunction,
+}
+
+impl Parse for TWFFIType {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let lookahead = input.lookahead1();
+        if lookahead.peek(keywords::static_function) {
+            input.parse::<keywords::static_function>()?;
+            Ok(Self::StaticFunction)
+        } else {
+            Err(lookahead.error())
+        }
+    }
+}
+
+impl fmt::Display for TWFFIType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            TWFFIType::StaticFunction => write!(f, "static_function"),
+        }
+    }
+}
+
+#[derive(Parse, Clone)]
+pub struct TWFFIAttrArgs {
+    pub _ty_keyword: Option<keywords::ty>,
+    #[parse_if(_ty_keyword.is_some())]
+    pub _eq: Option<Token![=]>,
+    #[parse_if(_ty_keyword.is_some())]
+    pub _ty: Option<TWFFIType>,
+    #[parse_if(_ty_keyword.is_some())]
+    pub _comma: Option<Token![,]>,
+
+    pub _class_keyword: Option<keywords::class>,
+    #[parse_if(_class_keyword.is_some())]
+    pub _eq2: Option<Token![=]>,
+    #[parse_if(_class_keyword.is_some())]
+    pub class: Option<Ident>,
+    #[parse_if(_class_keyword.is_some())]
+    pub _comma2: Option<Token![,]>,
+
+    pub _name_keyword: Option<keywords::name>,
+    #[parse_if(_name_keyword.is_some())]
+    pub _eq3: Option<Token![=]>,
+    #[parse_if(_name_keyword.is_some())]
+    pub name: Option<Ident>,
+}
+
+pub fn tw_ffi(attr: TokenStream2, item: TokenStream2) -> Result<TokenStream2> {
+    let args = parse2::<TWFFIAttrArgs>(attr)?;
+
+    let func = parse2::<syn::ItemFn>(item.clone())?;
+    let func_name = func.sig.ident.to_string();
+    let func_args = func
+        .sig
+        .inputs
+        .iter()
+        .map(|arg| match arg {
+            syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => (quote!( #pat ), quote!( #ty )),
+            _ => (quote!(), quote!()),
+        })
+        .map(|(name, ty)| TWArg {
+            name: name.to_string(),
+            ty: ty.to_string(),
+        })
+        .collect::<Vec<TWArg>>();
+
+    let return_type = func.sig.output;
+    let return_type = match return_type {
+        syn::ReturnType::Type(_, ty) => quote!(#ty).to_string(),
+        _ => "void".to_string(),
+    };
+
+    let class = args.class.unwrap().to_string();
+    let docs = func
+        .attrs
+        .iter()
+        .flat_map(|attr| {
+            if let syn::Meta::NameValue(meta) = &attr.meta {
+                if meta.path.is_ident("doc") {
+                    if let syn::Expr::Lit(syn::ExprLit {
+                        lit: syn::Lit::Str(lit),
+                        ..
+                    }) = &meta.value
+                    {
+                        return Some(lit.value().trim().to_string());
+                    }
+                }
+            }
+            None
+        })
+        .collect::<Vec<_>>();
+    let static_function = TWStaticFunction {
+        name: args.name.unwrap().to_string(),
+        rust_name: func_name,
+        args: func_args,
+        return_type,
+        docs,
+    };
+
+    if let Ok(out_dir) = env::var("CARGO_WORKSPACE_DIR") {
+        let bindings_dir = Path::new(&out_dir).join("bindings");
+        fs::create_dir_all(&bindings_dir).expect("Failed to create bindings directory");
+        let yaml_file_path = bindings_dir.join(format!("{}.yaml", class));
+
+        let mut config = if yaml_file_path.exists() {
+            serde_yaml::from_str(
+                &fs::read_to_string(&yaml_file_path).expect("Failed to read existing YAML file"),
+            )
+            .expect("Failed to parse YAML file")
+        } else {
+            TWConfig {
+                class,
+                static_functions: vec![],
+            }
+        };
+        if let Some(idx) = config
+            .static_functions
+            .iter()
+            .position(|f| f.name == static_function.name)
+        {
+            config.static_functions[idx] = static_function;
+        } else {
+            config.static_functions.push(static_function);
+        }
+
+        let yaml_output: String =
+            serde_yaml::to_string(&config).expect("Failed to serialize to YAML");
+        fs::write(&yaml_file_path, yaml_output).expect("Failed to write YAML file");
+    } else {
+        panic!("CARGO_WORKSPACE_DIR is not set");
+    }
+
+    Ok(item)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proc_macro2::Span;
+
+    #[test]
+    fn test_ffi_attr_arg_parsing() {
+        let args = parse2::<TWFFIAttrArgs>(quote! {
+            ty = static_function,
+            class = MyClass,
+            name = MyName
+        })
+        .unwrap();
+        assert_eq!(args.class, Some(Ident::new("MyClass", Span::call_site())));
+        assert_eq!(args.name, Some(Ident::new("MyName", Span::call_site())));
+    }
+}
diff --git a/rust/tw_memory/src/ffi/mod.rs b/rust/tw_memory/src/ffi/mod.rs
index c69f1550a6a..0ee0b6becba 100644
--- a/rust/tw_memory/src/ffi/mod.rs
+++ b/rust/tw_memory/src/ffi/mod.rs
@@ -54,3 +54,8 @@ pub trait RawPtrTrait: Sized {
         Some(Box::from_raw(raw))
     }
 }
+
+pub type Nullable<T> = *const T;
+pub type Nonnull<T> = *const T;
+pub type NullableMut<T> = *mut T;
+pub type NonnullMut<T> = *mut T;
diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml
index 1b8f34f853a..c66a335b140 100644
--- a/rust/wallet_core_rs/Cargo.toml
+++ b/rust/wallet_core_rs/Cargo.toml
@@ -43,6 +43,7 @@ tw_hash = { path = "../tw_hash", optional = true }
 tw_keypair = { path = "../tw_keypair", optional = true }
 tw_memory = { path = "../tw_memory", optional = true }
 tw_number = { path = "../tw_number", optional = true }
+tw_macros = { path = "../tw_macros" }
 tw_misc = { path = "../tw_misc" }
 tw_proto = { path = "../tw_proto", optional = true }
 tw_solana = { path = "../chains/tw_solana", optional = true }
diff --git a/rust/wallet_core_rs/src/ffi/ton/address_converter.rs b/rust/wallet_core_rs/src/ffi/ton/address_converter.rs
index f16ee89ac69..e8e1fe57699 100644
--- a/rust/wallet_core_rs/src/ffi/ton/address_converter.rs
+++ b/rust/wallet_core_rs/src/ffi/ton/address_converter.rs
@@ -5,8 +5,9 @@
 #![allow(clippy::missing_safety_doc)]
 
 use std::str::FromStr;
+use tw_macros::tw_ffi;
 use tw_memory::ffi::tw_string::TWString;
-use tw_memory::ffi::RawPtrTrait;
+use tw_memory::ffi::{Nonnull, NullableMut, RawPtrTrait};
 use tw_misc::try_or_else;
 use tw_ton::address::TonAddress;
 use tw_ton::modules::address_converter::AddressConverter;
@@ -17,10 +18,11 @@ use tw_ton::modules::address_converter::AddressConverter;
 ///
 /// \param address Address to be converted into a Bag Of Cells (BoC).
 /// \return Pointer to a base64 encoded Bag Of Cells (BoC). Null if invalid address provided.
+#[tw_ffi(ty = static_function, class = TWTONAddressConverter, name = ToBoc)]
 #[no_mangle]
 pub unsafe extern "C" fn tw_ton_address_converter_to_boc(
-    address: *const TWString,
-) -> *mut TWString {
+    address: Nonnull<TWString>,
+) -> NullableMut<TWString> {
     let address = try_or_else!(TWString::from_ptr_as_ref(address), std::ptr::null_mut);
     let address_str = try_or_else!(address.as_str(), std::ptr::null_mut);
     let address_ton = try_or_else!(TonAddress::from_str(address_str), std::ptr::null_mut);
@@ -39,8 +41,11 @@ pub unsafe extern "C" fn tw_ton_address_converter_to_boc(
 ///
 /// \param boc Base64 encoded Bag Of Cells (BoC).
 /// \return Pointer to a Jetton address.
+#[tw_ffi(ty = static_function, class = TWTONAddressConverter, name = FromBoc)]
 #[no_mangle]
-pub unsafe extern "C" fn tw_ton_address_converter_from_boc(boc: *const TWString) -> *mut TWString {
+pub unsafe extern "C" fn tw_ton_address_converter_from_boc(
+    boc: Nonnull<TWString>,
+) -> NullableMut<TWString> {
     let boc = try_or_else!(TWString::from_ptr_as_ref(boc), std::ptr::null_mut);
     let boc_str = try_or_else!(boc.as_str(), std::ptr::null_mut);
 
@@ -58,12 +63,13 @@ pub unsafe extern "C" fn tw_ton_address_converter_from_boc(boc: *const TWString)
 /// \param bounceable whether the result address should be bounceable.
 /// \param testnet whether the result address should be testnet.
 /// \return user-friendly address str.
+#[tw_ffi(ty = static_function, class = TWTONAddressConverter, name = ToUserFriendly)]
 #[no_mangle]
 pub unsafe extern "C" fn tw_ton_address_converter_to_user_friendly(
-    address: *const TWString,
+    address: Nonnull<TWString>,
     bounceable: bool,
     testnet: bool,
-) -> *mut TWString {
+) -> NullableMut<TWString> {
     let address = try_or_else!(TWString::from_ptr_as_ref(address), std::ptr::null_mut);
     let address_str = try_or_else!(address.as_str(), std::ptr::null_mut);
     let address_ton = try_or_else!(TonAddress::from_str(address_str), std::ptr::null_mut)
diff --git a/src/interface/TWTONAddressConverter.cpp b/src/interface/TWTONAddressConverter.cpp
deleted file mode 100644
index 4113448ad24..00000000000
--- a/src/interface/TWTONAddressConverter.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-//
-// Copyright © 2017 Trust Wallet.
-
-#include <TrustWalletCore/TWTONAddressConverter.h>
-
-#include "Base64.h"
-#include "rust/Wrapper.h"
-
-using namespace TW;
-
-TWString *_Nullable TWTONAddressConverterToBoc(TWString *_Nonnull address) {
-    auto& addressString = *reinterpret_cast<const std::string*>(address);
-
-    const Rust::TWStringWrapper addressRustStr = addressString;
-    const Rust::TWStringWrapper bocRustStr = Rust::tw_ton_address_converter_to_boc(addressRustStr.get());
-    if (!bocRustStr) {
-        return nullptr;
-    }
-
-    return TWStringCreateWithUTF8Bytes(bocRustStr.c_str());
-}
-
-TWString *_Nullable TWTONAddressConverterFromBoc(TWString *_Nonnull boc) {
-    auto& bocEncoded = *reinterpret_cast<const std::string*>(boc);
-
-    const Rust::TWStringWrapper bocRustStr = bocEncoded;
-    const Rust::TWStringWrapper addressRustStr = Rust::tw_ton_address_converter_from_boc(bocRustStr.get());
-    if (!addressRustStr) {
-        return nullptr;
-    }
-
-    return TWStringCreateWithUTF8Bytes(addressRustStr.c_str());
-}
-
-TWString *_Nullable TWTONAddressConverterToUserFriendly(TWString *_Nonnull address, bool bounceable, bool testnet) {
-    auto& addressString = *reinterpret_cast<const std::string*>(address);
-
-    const Rust::TWStringWrapper addressRustStr = addressString;
-    const Rust::TWStringWrapper userFriendlyRustStr = Rust::tw_ton_address_converter_to_user_friendly(addressRustStr.get(), bounceable, testnet);
-    if (!userFriendlyRustStr) {
-        return nullptr;
-    }
-
-    return TWStringCreateWithUTF8Bytes(userFriendlyRustStr.c_str());
-}
diff --git a/tools/generate-files b/tools/generate-files
index de3abc7f906..a987d9c19f6 100755
--- a/tools/generate-files
+++ b/tools/generate-files
@@ -56,15 +56,15 @@ mkdir -p swift/Sources/Generated/Protobuf swift/Sources/Generated/Enums
 # Generate coins info from registry.json
 codegen/bin/coins
 
+# Generate rust bindgen
+tools/rust-bindgen "$@"
+
 # Generate interface code, Swift bindings excluded.
 codegen/bin/codegen
 
 # Convert doxygen comments to appropriate format
 tools/doxygen_convert_comments
 
-# Generate rust bindgen
-tools/rust-bindgen "$@"
-
 # Generate Java, C++ and Swift Protobuf files
 
 if [ -x "$(command -v protoc-gen-swift)" ] && isTargetSpecified "ios"; then
diff --git a/tools/rust-bindgen b/tools/rust-bindgen
index 0edaf9c23a5..2ed44523398 100755
--- a/tools/rust-bindgen
+++ b/tools/rust-bindgen
@@ -72,6 +72,9 @@ cbindgen --crate $CRATE --output ../src/rust/bindgen/$HEADER_NAME
 cd -
 [[ -e rust/target/release/${TARGET_NAME} ]] && cp rust/target/release/${TARGET_NAME} build/local/lib/
 
+echo "Generating C++ files..."
+pushd codegen-v2 && cargo run -- cpp && popd
+
 if isTargetSpecified "ios" && [[ $(uname) == "Darwin" ]]; then
 cd rust
 cat > $TARGET_XCFRAMEWORK_NAME/Info.plist << EOF
@@ -146,3 +149,4 @@ cat > $TARGET_XCFRAMEWORK_NAME/Info.plist << EOF
 EOF
 cd -
 fi
+