Skip to content

Commit 11bd2fb

Browse files
authored
Adds flutter bindings (trustwallet#4412)
* Adds flutter bindings * Adds sample and CI * Adds tests * Add env * fix copy step
1 parent 2232874 commit 11bd2fb

17 files changed

+922
-3
lines changed

.github/workflows/flutter-ci.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Flutter CI
2+
3+
on:
4+
push:
5+
branches: [ dev, master ]
6+
pull_request:
7+
branches: [ dev, master ]
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
build:
15+
runs-on: ubuntu-24.04
16+
if: github.event.pull_request.draft == false
17+
steps:
18+
- uses: actions/checkout@v3
19+
- name: Install system dependencies
20+
run: |
21+
tools/install-sys-dependencies-linux
22+
tools/install-rust-dependencies
23+
- name: Cache internal dependencies
24+
id: internal_cache
25+
uses: actions/cache@v3
26+
with:
27+
path: build/local
28+
key: ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('tools/install-sys-dependencies-linux') }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }}
29+
- name: Install internal dependencies
30+
run: |
31+
tools/install-dependencies
32+
env:
33+
CC: /usr/bin/clang
34+
CXX: /usr/bin/clang++
35+
if: steps.internal_cache.outputs.cache-hit != 'true'
36+
37+
- name: Cache Rust
38+
uses: Swatinem/rust-cache@v2
39+
with:
40+
workspaces: |
41+
rust
42+
43+
- name: Code generation
44+
run: |
45+
tools/generate-files native
46+
env:
47+
CC: /usr/bin/clang
48+
CXX: /usr/bin/clang++
49+
50+
- name: Setup Dart
51+
uses: dart-lang/setup-dart@v1
52+
with:
53+
sdk: '3.8.1'
54+
cache: true
55+
cache-key: dart-3.8.1
56+
cache-path: ${{ github.workspace }}/.pub-cache
57+
58+
- name: Install Dart dependencies
59+
run: |
60+
cd flutter
61+
dart pub get
62+
dart pub upgrade
63+
64+
- name: Flutter build
65+
run: |
66+
tools/flutter-build
67+
env:
68+
CC: /usr/bin/clang
69+
CXX: /usr/bin/clang++
70+

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ elseif (${TW_COMPILE_JAVA})
9191
find_package(JNI REQUIRED)
9292
target_include_directories(TrustWalletCore PRIVATE ${JNI_INCLUDE_DIRS})
9393
target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto protobuf Boost::boost)
94+
elseif (${FLUTTER})
95+
message("Configuring for Flutter")
96+
file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h)
97+
add_library(TrustWalletCore SHARED ${sources} ${PROTO_SRCS} ${PROTO_HDRS})
98+
target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto protobuf Boost::boost)
9499
else ()
95100
message("Configuring standalone")
96101
file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h)

cmake/StandardSettings.cmake

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#
22
# Default settings
33
#
4-
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
4+
if (NOT FLUTTER)
5+
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
6+
endif ()
57
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
68
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
79
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version" FORCE)
@@ -66,7 +68,7 @@ endif ()
6668
option(TW_UNIT_TESTS "Enable the unit tests of the project" ON)
6769
option(TW_BUILD_EXAMPLES "Enable the examples builds of the project" ON)
6870

69-
if (ANDROID OR IOS_PLATFORM OR TW_COMPILE_WASM OR TW_COMPILE_JAVA)
71+
if (ANDROID OR IOS_PLATFORM OR TW_COMPILE_WASM OR TW_COMPILE_JAVA OR FLUTTER)
7072
set(TW_UNIT_TESTS OFF)
7173
set(TW_BUILD_EXAMPLES OFF)
7274
endif()

flutter/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# https://dart.dev/guides/libraries/private-files
2+
# Created by `dart pub`
3+
.dart_tool/
4+
*.dylib
5+
wallet_core_bindings.dart

flutter/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 1.0.0
2+
3+
- Initial version.

flutter/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Wallet Core Bindings for Flutter
2+
3+
## Installation
4+
5+
1. Install Dart SDK:
6+
- Visit [Dart SDK installation page](https://dart.dev/get-dart)
7+
- Follow the instructions for your operating system
8+
9+
2. Install dependencies:
10+
```bash
11+
dart pub get
12+
```
13+
14+
## Usage
15+
16+
### Running the App
17+
```bash
18+
dart run
19+
```
20+
21+
### Test
22+
```bash
23+
dart test
24+
```

flutter/analysis_options.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file configures the static analysis results for your project (errors,
2+
# warnings, and lints).
3+
#
4+
# This enables the 'recommended' set of lints from `package:lints`.
5+
# This set helps identify many issues that may lead to problems when running
6+
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
7+
# style and format.
8+
#
9+
# If you want a smaller set of lints you can change this to specify
10+
# 'package:lints/core.yaml'. These are just the most critical lints
11+
# (the recommended set includes the core lints).
12+
# The core lints are also what is used by pub.dev for scoring packages.
13+
14+
include: package:lints/recommended.yaml
15+
16+
# Uncomment the following section to specify additional rules.
17+
18+
# linter:
19+
# rules:
20+
# - camel_case_types
21+
22+
# analyzer:
23+
# exclude:
24+
# - path/to/excluded/files/**
25+
26+
# For more information about the core and recommended set of lints, see
27+
# https://dart.dev/go/core-lints
28+
29+
# For additional information about configuring this file, see
30+
# https://dart.dev/guides/language/analysis-options

flutter/bin/flutter.dart

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import 'package:flutter/wallet_core.dart';
2+
import 'package:flutter/wallet_core_bindings.dart';
3+
4+
import 'dart:ffi';
5+
import 'package:ffi/ffi.dart';
6+
7+
void main(List<String> arguments) {
8+
final walletCore = load();
9+
10+
final passwordTwString = walletCore.TWStringCreateWithUTF8Bytes(
11+
"".toNativeUtf8().cast<Char>(),
12+
);
13+
14+
print("Creating a new HD wallet ... ");
15+
final walletNew = walletCore.TWHDWalletCreate(128, passwordTwString);
16+
print("done.");
17+
print(
18+
"Secret mnemonic for new wallet: '${walletCore.TWStringUTF8Bytes(walletCore.TWHDWalletMnemonic(walletNew)).cast<Utf8>().toDartString()}'.",
19+
);
20+
walletCore.TWHDWalletDelete(walletNew);
21+
22+
// Alternative: Import wallet with existing recovery phrase (mnemonic)
23+
print("Importing an HD wallet from earlier ... ");
24+
final secretMnemonic = walletCore.TWStringCreateWithUTF8Bytes(
25+
"ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"
26+
.toNativeUtf8()
27+
.cast<Char>(),
28+
);
29+
final walletImp = walletCore.TWHDWalletCreateWithMnemonic(
30+
secretMnemonic,
31+
walletCore.TWStringCreateWithUTF8Bytes("".toNativeUtf8().cast<Char>()),
32+
);
33+
walletCore.TWStringDelete(secretMnemonic);
34+
print("done.");
35+
print(
36+
"Secret mnemonic for imported wallet: '${walletCore.TWStringUTF8Bytes(walletCore.TWHDWalletMnemonic(walletImp)).cast<Utf8>().toDartString()}'.",
37+
);
38+
39+
// coin type: we use Ethereum
40+
const coinType = TWCoinType.TWCoinTypeEthereum;
41+
print(
42+
"Working with coin: ${walletCore.TWStringUTF8Bytes(walletCore.TWCoinTypeConfigurationGetName(coinType)).cast<Utf8>().toDartString()} ${walletCore.TWStringUTF8Bytes(walletCore.TWCoinTypeConfigurationGetSymbol(coinType)).cast<Utf8>().toDartString()}",
43+
);
44+
45+
// Derive default address
46+
print("Obtaining default address ... ");
47+
final address = walletCore.TWStringUTF8Bytes(
48+
walletCore.TWHDWalletGetAddressForCoin(walletImp, coinType),
49+
).cast<Utf8>().toDartString();
50+
print("done.");
51+
print("Default address: '$address'");
52+
53+
// Alternative: Derive address using default derivation path
54+
// Done in 2 steps: derive private key, then address from private key
55+
// Note that private key is passed around between the two calls by the wallet -- be always cautious when handling secrets, avoid the risk of leaking secrets
56+
print(
57+
"Default derivation path: ${walletCore.TWStringUTF8Bytes(walletCore.TWCoinTypeDerivationPath(coinType)).cast<Utf8>().toDartString()}",
58+
);
59+
final secretPrivateKeyDefault = walletCore.TWHDWalletGetKeyForCoin(
60+
walletImp,
61+
coinType,
62+
);
63+
final addressDefault = walletCore.TWStringUTF8Bytes(
64+
walletCore.TWCoinTypeDeriveAddress(coinType, secretPrivateKeyDefault),
65+
).cast<Utf8>().toDartString();
66+
print("Address from default key: '$addressDefault'");
67+
68+
// Alternative: Derive address using custom derivation path
69+
final customDerivationPath = walletCore.TWStringCreateWithUTF8Bytes(
70+
"m/44'/60'/1'/0/0".toNativeUtf8().cast<Char>(),
71+
);
72+
final secretPrivateKeyCustom = walletCore.TWHDWalletGetKey(
73+
walletImp,
74+
coinType,
75+
customDerivationPath,
76+
);
77+
walletCore.TWStringDelete(customDerivationPath);
78+
final addressCustom = walletCore.TWStringUTF8Bytes(
79+
walletCore.TWCoinTypeDeriveAddress(coinType, secretPrivateKeyCustom),
80+
).cast<Utf8>().toDartString();
81+
print("Custom-derived address: '$addressCustom'");
82+
print("");
83+
84+
print(
85+
"RECEIVE funds: Perform send from somewhere else to this address: $address",
86+
);
87+
print("");
88+
89+
// Steps for sending:
90+
// 1. put together a send message (contains sender and receiver address, amount, gas price, etc.)
91+
// 2. sign this message
92+
// 3. broadcast this message to the P2P network -- not done in this sample
93+
print("SEND funds:");
94+
const dummyReceiverAddress = "0xC37054b3b48C3317082E7ba872d7753D13da4986";
95+
final secretPrivKey = walletCore.TWPrivateKeyData(secretPrivateKeyDefault);
96+
97+
print("preparing transaction (using AnySigner) ... ");
98+
const chainIdB64 = "AQ=="; // base64(parse_hex("01"))
99+
const gasPriceB64 =
100+
"1pOkAA=="; // base64(parse_hex("d693a4")) decimal 3600000000
101+
const gasLimitB64 = "Ugg="; // base64(parse_hex("5208")) decimal 21000
102+
const amountB64 =
103+
"A0i8paFgAA=="; // base64(parse_hex("0348bca5a160")) 924400000000000
104+
final transaction =
105+
"{"
106+
"\"chainId\":\"$chainIdB64"
107+
"\",\"gasPrice\":\"$gasPriceB64"
108+
"\",\"gasLimit\":\"$gasLimitB64"
109+
"\",\"toAddress\":\"$dummyReceiverAddress"
110+
"\",\"transaction\":{\"transfer\":{\"amount\":\"$amountB64"
111+
"\"}}}";
112+
print("transaction: $transaction");
113+
114+
print("signing transaction ... ");
115+
final json = walletCore.TWStringCreateWithUTF8Bytes(
116+
transaction.toNativeUtf8().cast<Char>(),
117+
);
118+
final result = walletCore.TWAnySignerSignJSON(
119+
json,
120+
secretPrivKey,
121+
TWCoinType.TWCoinTypeEthereum,
122+
);
123+
final signedTransaction = walletCore.TWStringUTF8Bytes(
124+
result,
125+
).cast<Utf8>().toDartString();
126+
print("done");
127+
print(
128+
"Signed transaction data (to be broadcast to network): (len ${signedTransaction.length}) '$signedTransaction'",
129+
);
130+
// see e.g. https://github.com/flightwallet/decode-eth-tx for checking binary output content
131+
print("");
132+
walletCore.TWStringDelete(json);
133+
walletCore.TWStringDelete(result);
134+
135+
print("Bye!");
136+
walletCore.TWHDWalletDelete(walletImp);
137+
138+
walletCore.TWStringDelete(passwordTwString);
139+
}

flutter/config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
output: 'lib/wallet_core_bindings.dart'
2+
headers:
3+
entry-points:
4+
- 'include/**.h'
5+
name: 'WalletCore'
6+
description: 'Bindings to WalletCore'
7+
silence-enum-warning: true

flutter/include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../include

0 commit comments

Comments
 (0)