Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions flutter_wallet_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Flutter Wallet Example

This example shows how to build a simple Flutter wallet application using the Rust SDK.

The Rust side exposes a small FFI layer compiled as a dynamic library using `cdylib`. The
Flutter application calls into this library using `dart:ffi`.

The default Ark server is **https://mutinynet.arkade.sh**. The example connects to this
server when initializing the client.

## Structure

- `lib/main.dart` – minimal Flutter UI that loads the dynamic library and displays an
off‑chain address from the wallet.
- `native` – Rust crate compiled as a dynamic library. It wraps `ark-client` and provides
FFI bindings.

## Building

```
flutter run
```

Ensure you have Rust and Flutter installed. The Rust crate builds as part of the Flutter
project via `cargo`.

49 changes: 49 additions & 0 deletions flutter_wallet_example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'dart:ffi';
import 'package:flutter/material.dart';

// Load the native library. In a real application the path would depend on the
// platform (Android/iOS/macOS). For simplicity this uses `DynamicLibrary.process`.
final DynamicLibrary native = DynamicLibrary.process();

typedef _InitClientNative = Void Function();

typedef _GetOffchainAddressNative = Pointer<Utf8> Function();

final _initClient = native.lookupFunction<_InitClientNative, void Function()>('init_client');
final _getOffchainAddress = native
.lookupFunction<_GetOffchainAddressNative, Pointer<Utf8> Function()>('get_offchain_address');

void main() {
runApp(const MyApp());
}

class MyApp extends StatefulWidget {
const MyApp({super.key});

@override
State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
String address = '';

@override
void initState() {
super.initState();
_initClient();
final ptr = _getOffchainAddress();
address = ptr.cast<Utf8>().toDartString();
}

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Ark Flutter Example')),
body: Center(
child: Text(address.isEmpty ? 'Loading...' : address),
),
),
);
}
}
15 changes: 15 additions & 0 deletions flutter_wallet_example/native/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "ark_flutter_example"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
ark-client = { path = "../../ark-client" }
ark-bdk-wallet = { path = "../../ark-bdk-wallet" }
ark-core = { path = "../../ark-core" }
bitcoin = "0.30"
async-trait = "0.1"
tokio = { version = "1.41", features = ["rt", "macros"] }
150 changes: 150 additions & 0 deletions flutter_wallet_example/native/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::sync::Arc;

use ark_client::{Client, OfflineClient};
use ark_core::ArkAddress;
use bitcoin::key::Keypair;
use bitcoin::secp256k1::{Secp256k1, SecretKey};
use bitcoin::Address;

struct SimpleBlockchain;

#[async_trait::async_trait]
impl ark_client::Blockchain for SimpleBlockchain {
async fn find_outpoints(
&self,
_address: &Address,
) -> Result<Vec<ark_client::ExplorerUtxo>, ark_client::Error> {
Ok(vec![])
}

async fn find_tx(
&self,
_txid: &bitcoin::Txid,
) -> Result<Option<bitcoin::Transaction>, ark_client::Error> {
Ok(None)
}

async fn get_output_status(
&self,
_txid: &bitcoin::Txid,
_vout: u32,
) -> Result<ark_client::SpendStatus, ark_client::Error> {
Ok(ark_client::SpendStatus { spend_txid: None })
}

async fn broadcast(&self, _tx: &bitcoin::Transaction) -> Result<(), ark_client::Error> {
Ok(())
}

async fn get_fee_rate(&self) -> Result<f64, ark_client::Error> {
Ok(1.0)
}

async fn broadcast_package(
&self,
_txs: &[&bitcoin::Transaction],
) -> Result<(), ark_client::Error> {
Ok(())
}
}

struct DummyWallet;

impl ark_client::wallet::OnchainWallet for DummyWallet {
fn get_onchain_address(&self) -> Result<Address, ark_client::Error> {
Ok("bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp3whfpx"
.parse()
.unwrap())
}

async fn sync(&self) -> Result<(), ark_client::Error> {
Ok(())
}

fn balance(&self) -> Result<ark_client::wallet::Balance, ark_client::Error> {
Ok(ark_client::wallet::Balance::default())
}

fn prepare_send_to_address(
&self,
_address: Address,
_amount: bitcoin::Amount,
_fee_rate: bitcoin::FeeRate,
) -> Result<bitcoin::psbt::PartiallySignedTransaction, ark_client::Error> {
Err(ark_client::Error::ad_hoc("not implemented"))
}

fn sign(
&self,
_psbt: &mut bitcoin::psbt::PartiallySignedTransaction,
) -> Result<bool, ark_client::Error> {
Ok(false)
}

fn select_coins(
&self,
_target_amount: bitcoin::Amount,
) -> Result<ark_core::UtxoCoinSelection, ark_client::Error> {
Err(ark_client::Error::ad_hoc("not implemented"))
}
}

impl ark_client::wallet::BoardingWallet for DummyWallet {
fn new_boarding_output(
&self,
_server_pk: bitcoin::XOnlyPublicKey,
_exit_delay: bitcoin::Sequence,
_network: bitcoin::Network,
) -> Result<ark_core::BoardingOutput, ark_client::Error> {
Err(ark_client::Error::ad_hoc("not implemented"))
}

fn get_boarding_outputs(&self) -> Result<Vec<ark_core::BoardingOutput>, ark_client::Error> {
Ok(vec![])
}

fn sign_for_pk(
&self,
_pk: &bitcoin::XOnlyPublicKey,
_msg: &bitcoin::secp256k1::Message,
) -> Result<bitcoin::secp256k1::schnorr::Signature, ark_client::Error> {
Err(ark_client::Error::ad_hoc("not implemented"))
}
}

static mut CLIENT: Option<Client<SimpleBlockchain, DummyWallet>> = None;

#[no_mangle]
pub extern "C" fn init_client() {
let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(&[1u8; 32]).unwrap();
let keypair = Keypair::from_secret_key(&secp, &secret_key);

let blockchain = Arc::new(SimpleBlockchain);
let wallet = Arc::new(DummyWallet);

let offline = OfflineClient::new(
"flutter".to_string(),
keypair,
blockchain,
wallet,
"https://mutinynet.arkade.sh".to_string(),
);

let rt = tokio::runtime::Runtime::new().unwrap();
let client = rt.block_on(async move { offline.connect().await.unwrap() });

unsafe {
CLIENT = Some(client);
}
}

#[no_mangle]
pub extern "C" fn get_offchain_address() -> *const c_char {
let client = unsafe { CLIENT.as_ref().unwrap() };
let (addr, _) = client.get_offchain_address().unwrap();
let s = addr.to_string();
CString::new(s).unwrap().into_raw()
}
14 changes: 14 additions & 0 deletions flutter_wallet_example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: flutter_wallet_example
description: A simple Flutter wallet example using the Ark Rust SDK
publish_to: 'none'
version: 0.1.0

environment:
sdk: '>=3.0.0 <4.0.0'

dependencies:
flutter:
sdk: flutter

flutter:
uses-material-design: true
Loading