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
25 changes: 25 additions & 0 deletions src/Angor/Avalonia/Angor.Sdk.Wasm/Angor.Sdk.Wasm.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<!-- Use interpreted mode for better compatibility -->
<RunAOTCompilation>false</RunAOTCompilation>

<!-- Reduce bundle size -->
<InvariantGlobalization>true</InvariantGlobalization>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Angor.Sdk\Angor.Sdk.csproj" />
</ItemGroup>

</Project>
186 changes: 186 additions & 0 deletions src/Angor/Avalonia/Angor.Sdk.Wasm/AngorSdkInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
using System.Text.Json;
using Microsoft.JSInterop;

namespace Angor.Sdk.Wasm;

/// <summary>
/// JavaScript interop bindings for Angor SDK.
/// Methods decorated with [JSInvokable] are callable from TypeScript/JavaScript.
/// </summary>
public class AngorSdkInterop
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
};

/// <summary>
/// Initialize the SDK. Must be called before using other methods.
/// </summary>
[JSInvokable]
public static string Initialize(string network)
{
try
{
// TODO: Initialize SDK services with the specified network (mainnet/testnet)
return JsonSerializer.Serialize(new { success = true, message = "SDK initialized", network }, JsonOptions);
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { success = false, error = ex.Message }, JsonOptions);
}
}

/// <summary>
/// Generate a new wallet with seed words.
/// </summary>
[JSInvokable]
public static string GenerateWallet(int wordCount = 12)
{
try
{
// TODO: Use Angor.Sdk wallet generation
// var hdOperations = new HdOperations();
// var words = hdOperations.GenerateWords(wordCount);

return JsonSerializer.Serialize(new
{
success = true,
// seedWords = words,
message = "Wallet generation not yet implemented",
wordCount
}, JsonOptions);
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { success = false, error = ex.Message }, JsonOptions);
}
}

/// <summary>
/// Get project details by project ID.
/// </summary>
[JSInvokable]
public static async Task<string> GetProject(string projectId)
{
try
{
// TODO: Use IProjectService to fetch project
await Task.CompletedTask;

return JsonSerializer.Serialize(new
{
success = true,
message = "GetProject not yet implemented",
projectId
}, JsonOptions);
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { success = false, error = ex.Message }, JsonOptions);
}
}

/// <summary>
/// Create an investment transaction draft.
/// Note: Using double for satoshi amounts for JavaScript compatibility.
/// JavaScript's Number can safely represent integers up to 2^53-1, which is ~90 million BTC in sats.
/// </summary>
[JSInvokable]
public static async Task<string> CreateInvestment(
string walletId,
string projectId,
double amountSats,
double feeRateSatsPerVb)
{
try
{
// Convert from double to long for internal use
var amountSatsLong = (long)amountSats;
var feeRateLong = (long)feeRateSatsPerVb;

// TODO: Use IInvestmentAppService to create investment
await Task.CompletedTask;

return JsonSerializer.Serialize(new
{
success = true,
message = "CreateInvestment not yet implemented",
walletId,
projectId,
amountSats = amountSatsLong,
feeRateSatsPerVb = feeRateLong
}, JsonOptions);
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { success = false, error = ex.Message }, JsonOptions);
}
}

/// <summary>
/// Sign a transaction with wallet credentials.
/// </summary>
[JSInvokable]
public static string SignTransaction(string transactionHex, string walletSeedWords)
{
try
{
// TODO: Use signing services
return JsonSerializer.Serialize(new
{
success = true,
message = "SignTransaction not yet implemented"
}, JsonOptions);
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { success = false, error = ex.Message }, JsonOptions);
}
}

/// <summary>
/// Broadcast a signed transaction.
/// </summary>
[JSInvokable]
public static async Task<string> BroadcastTransaction(string signedTransactionHex)
{
try
{
// TODO: Use ITransactionService to broadcast
await Task.CompletedTask;

return JsonSerializer.Serialize(new
{
success = true,
message = "BroadcastTransaction not yet implemented"
}, JsonOptions);
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { success = false, error = ex.Message }, JsonOptions);
}
}

/// <summary>
/// Derive project keys for a founder.
/// </summary>
[JSInvokable]
public static string DeriveProjectKeys(string walletSeedWords, string angorRootKey)
{
try
{
// TODO: Use IDerivationOperations
return JsonSerializer.Serialize(new
{
success = true,
message = "DeriveProjectKeys not yet implemented"
}, JsonOptions);
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { success = false, error = ex.Message }, JsonOptions);
}
}
}
12 changes: 12 additions & 0 deletions src/Angor/Avalonia/Angor.Sdk.Wasm/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Angor.Sdk.Wasm;

var builder = WebAssemblyHostBuilder.CreateDefault(args);

// Configure services if needed
// builder.Services.AddSingleton<IProjectService, ProjectService>();

Console.WriteLine("Angor SDK WASM initialized");

await builder.Build().RunAsync();
143 changes: 143 additions & 0 deletions src/Angor/Avalonia/Angor.Sdk.Wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Angor SDK WebAssembly

This project compiles the Angor SDK to WebAssembly (WASM) for use in TypeScript/JavaScript applications.

## Build

```bash
cd Angor.Sdk.Wasm
dotnet build
```

The output will be in `bin/Debug/net9.0/wwwroot/_framework/`.

## Production Build

For a smaller, optimized bundle:

```bash
dotnet publish -c Release
```

## Usage in TypeScript/JavaScript

### Option 1: Script Tag

```html
<!DOCTYPE html>
<html>
<head>
<base href="/" />
</head>
<body>
<script src="_framework/blazor.webassembly.js"></script>
<script src="angor-sdk.js"></script>
<script>
async function main() {
const sdk = await loadAngorSdk();

// Initialize with network
const initResult = await sdk.initialize('testnet');
console.log('SDK initialized:', initResult);

// Generate a wallet
const wallet = await sdk.generateWallet(12);
console.log('Wallet:', wallet);

// Get project details
const project = await sdk.getProject('npub1...');
console.log('Project:', project);
}

main().catch(console.error);
</script>
</body>
</html>
```

### Option 2: ES Module (TypeScript)

```typescript
import { loadAngorSdk, IAngorSdk, SdkResult } from './angor-sdk';

async function main(): Promise<void> {
const sdk: IAngorSdk = await loadAngorSdk();

// Initialize the SDK
const result = await sdk.initialize('testnet');
if (!result.success) {
console.error('Failed to initialize:', result.error);
return;
}

// Create an investment
const investment = await sdk.createInvestment(
'wallet-id',
'project-id',
100000, // 100,000 sats
5 // 5 sat/vB fee rate
);

console.log('Investment draft:', investment);
}
```

## API Reference

### `loadAngorSdk(): Promise<IAngorSdk>`

Loads and initializes the WASM module. Must be called before using any SDK methods.

### `IAngorSdk.initialize(network: 'mainnet' | 'testnet'): Promise<SdkResult>`

Initialize the SDK with the specified Bitcoin network.

### `IAngorSdk.generateWallet(wordCount?: 12 | 24): Promise<SdkResult<WalletInfo>>`

Generate a new HD wallet with BIP39 seed words.

### `IAngorSdk.getProject(projectId: string): Promise<SdkResult<ProjectInfo>>`

Fetch project details by Angor project ID.

### `IAngorSdk.createInvestment(...): Promise<SdkResult<InvestmentDraft>>`

Create an investment transaction draft for a project.

### `IAngorSdk.signTransaction(...): Promise<SdkResult<SignedTransaction>>`

Sign a transaction with wallet seed words.

### `IAngorSdk.broadcastTransaction(...): Promise<SdkResult<BroadcastResult>>`

Broadcast a signed transaction to the Bitcoin network.

### `IAngorSdk.deriveProjectKeys(...): Promise<SdkResult<ProjectKeys>>`

Derive project-specific keys for founders.

## Type Definitions

TypeScript type definitions are available in `typescript/angor-sdk.d.ts`.

## Bundle Size

The uncompressed bundle is large due to including the .NET runtime. For production:

1. Use gzip compression (`.gz` files are pre-generated)
2. Enable HTTP/2 for parallel loading
3. Consider using a CDN

## Browser Compatibility

Requires browsers with WebAssembly support:
- Chrome 57+
- Firefox 52+
- Safari 11+
- Edge 16+

## Notes

- The SDK runs entirely in the browser - no server required
- Private keys never leave the browser
- All Bitcoin transactions are constructed and signed client-side
Loading
Loading