Skip to content

Commit fc4c441

Browse files
committed
Move rgb assets methods to rln level
1 parent 3eeaaef commit fc4c441

16 files changed

Lines changed: 627 additions & 471 deletions

bindings/wasm-sdk/SDK_WASM_ENDPOINT_MATRIX.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ Core SDK behavior has been rewritten in wasm where native runtime assumptions do
4545
| `send_rgb` | `sendBegin` + `sendEndValue/Json` | runtime-backed | Two-step PSBT flow. |
4646
| `send_rgb_from_groups` | `walletSendRgbFromGroupsValue` / `walletSendRgbFromGroupsJson` and wallet-handle `sendRgbFromGroupsValue` / `sendRgbFromGroupsJson` | runtime-backed | Adapter implemented on wallet surfaces via real `send_begin` orchestration; legacy SDK-only `sendRgbFromGroups*` remains compatibility-only/unsupported on wasm facade. |
4747
| `init` | `initValue` / `initJson` | runtime-backed | Stateful wasm lifecycle initialization (`password+mnemonic` bootstrap) with stable validation/error contracts and runtime-session authority propagation (`initialized=true`, `authorized=false`). |
48-
| `unlock` | `unlock` | runtime-backed | Runtime-backed lifecycle unlock with request parsing, init precondition, password validation, and runtime-session authorization transition (`authorized=true`). |
48+
| `unlock` | `unlock` | runtime-backed | Runtime-backed lifecycle unlock with request parsing, init precondition, password validation, runtime-session authorization transition (`authorized=true`), and default RGB wallet bootstrap from lifecycle mnemonic (auto-attached to newly created nodes). |
4949
| `lock` | `lock` | runtime-backed | Runtime-backed lifecycle lock with init precondition and deterministic authorization transition (`authorized=false`) so runtime managers reject locked sessions until next unlock. |
5050
| `connect_peer` | `connectPeer` | runtime-backed | Runtime-backed websocket peer-connect path with secp256k1/`host:port` validation and runtime-event application through shared transport pipeline. On `ldk_bridge`, peer state is runtime-manager authoritative and reconnect aliases reactivate persisted peers (`started=true`), with JSON/text alias compatibility for event/id fields and separator-normalized kinds. |
5151
| `disconnect_peer` | `disconnectPeer` | runtime-backed | Runtime-backed disconnect path with secp256k1 validation and `peer_disconnected` transport transition application, including stale channel cleanup and runtime-manager-authoritative state transitions on `ldk_bridge`. |
5252
| `close_channel` | `closeChannel` | runtime-backed | Runtime-backed channel-close transition via `channel_closed` transport event path with runtime-manager-authoritative application on `ldk_bridge`, runtime-ready guard parity, and stable not-found contract. Extended options path (`closeChannelWithOptions`) supports native-like peer/session validation, virtual-session status transitions (`active -> abandon_pending -> abandoned`), and `force=true` rejection for trusted virtual channels. |
5353
| `create_utxos` | `createUtxosBegin` + `createUtxosEnd/Json` | runtime-backed | Two-step PSBT flow. |
54-
| `issue_asset_nia` | `walletIssueAssetNiaValue` / `walletIssueAssetNiaJson` and wallet-handle `issueAssetNiaValue` / `issueAssetNiaJson` | runtime-backed | Wired to `rgb-lib-wasm` `Wallet::issue_asset_nia`; legacy SDK-only `issueAssetNia*` remains compatibility-only/unsupported on wasm facade. |
55-
| `issue_asset_cfa` | `walletIssueAssetCfaValue` / `walletIssueAssetCfaJson` and wallet-handle `issueAssetCfaValue` / `issueAssetCfaJson` | runtime-backed | Wired via `rgb-lib-wasm` `Wallet::issue_asset_ifa` compatibility mapping; legacy SDK-only `issueAssetCfa*` remains compatibility-only/unsupported on wasm facade. |
54+
| `issue_asset_nia` | node/facade `issueAssetNiaValue` / `issueAssetNiaJson` | runtime-backed | Node-level adapter wired to `rgb-lib-wasm` `Wallet::issue_asset_nia`; default wallet is bootstrapped on `unlock`, auto-attached to created nodes, and lazily attached on first issuance call if missing (manual `attachWallet` still supported to override). |
55+
| `issue_asset_cfa` | node/facade `issueAssetCfaValue` / `issueAssetCfaJson` | runtime-backed | Node-level adapter wired via `rgb-lib-wasm` `Wallet::issue_asset_ifa` compatibility mapping; default wallet is bootstrapped on `unlock`, auto-attached to created nodes, and lazily attached on first issuance call if missing (manual `attachWallet` still supported to override). |
5656
| `issue_asset_uda` | `walletIssueAssetUdaValue` / `walletIssueAssetUdaJson` and wallet-handle `issueAssetUdaValue` / `issueAssetUdaJson` | unsupported-by-design | Explicit unsupported contract: `rgb-lib-wasm` currently has no UDA issuance primitive; legacy SDK-only `issueAssetUda*` remains compatibility-only/unsupported on wasm facade. |
5757
| `keysend` | `keysendValue` / `keysendJson` | runtime-backed | Runtime-backed payment send path enforcing native min amount parity (`SDK_HTLC_MIN_MSAT`), destination pubkey validation, RGB payload validation (`asset_id` format + `asset_amount > 0`), and no-route failure transitions through runtime payment-status events. On `ldk_bridge`, delivery requires connected destination peer state (`peer.started=true`). |
5858
| `send_btc` | `sendBtcBegin` + `sendBtcEnd` | runtime-backed | Two-step PSBT flow. |
@@ -160,7 +160,7 @@ The endpoints below are intentionally unsupported-by-design and have explicit ra
160160
|---|---|---|---|
161161
| UDA issuance | legacy sdk `issueAssetUdaValue/Json`; wallet surfaces `walletIssueAssetUdaValue/Json` / handle `issueAssetUdaValue/Json` | Legacy RLN issuance adapter unavailable in wasm; `rgb-lib-wasm` does not expose UDA issuance primitive. | sdk facade, sdk wallet-facade, wallet-handle |
162162
| Legacy grouped transfer endpoint | legacy sdk `sendRgbFromGroupsValue/Json` | Legacy grouped SDK adapter unavailable in wasm facade; runtime-backed grouped send is available on wallet surfaces (`walletSendRgbFromGroups*` / handle). | sdk facade, wallet-handle (runtime-backed path) |
163-
| Native host-only virtual cleanup proofs | `openChannel*` / `closeChannel*` node surfaces | Wasm runtime implements virtual draft/session lifecycle + reconciliation and now enforces best-effort preflight guards from runtime payment ledger (`pending` HTLC block + net counterparty BTC/RGB value floor checks since session creation). Full native host-only proofs that depend on filesystem RGB temp artifacts and exact counterparty floor internals are still intentionally not replicated in browser runtime. | Node-level contract tests for virtual session lifecycle + validation are present |
163+
| Native host-only virtual cleanup proofs | `openChannel*` / `closeChannel*` node surfaces | Wasm runtime implements virtual draft/session lifecycle + reconciliation and enforces conservative preflight guards from runtime payment ledger (`pending/claimable/claiming` HTLC block + net counterparty BTC/RGB value floor checks since session creation). Full native host-only proofs that depend on filesystem RGB temp artifacts and exact counterparty floor internals are still intentionally not replicated in browser runtime. | Node-level contract tests for virtual session lifecycle + validation are present |
164164

165165
## Wasm-Only Node Runtime Helpers
166166

bindings/wasm-sdk/examples/wasm-interop/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ window.signPsbt = async (unsignedPsbt) => {
105105
```
106106

107107
In real usage, replace this with your wallet/hardware signer integration.
108-
5. The RGB transfer page now pre-fills defaults for:
108+
5. The RGB transfer page uses fixed constants in JS for:
109109
- `Indexer URL`: `http://127.0.0.1:3002`
110110
- `RGB transport endpoint`: `rpc://127.0.0.1:3000/json-rpc`
111-
- sender/receiver `walletData` JSON generated at runtime via `rgbGenerateKeysValue("regtest")`
111+
- sender/receiver RLN instances are initialized at runtime, each with generated keys and a generated wallet bootstrap

bindings/wasm-sdk/examples/wasm-interop/manual_js_rgb_asset_transfer.js

Lines changed: 96 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import init, {
22
RlnWasmInvoice,
33
RlnWasmSdk,
4-
rgbGenerateKeysValue,
4+
rgbRestoreKeysValue,
55
} from "../../pkg/rln_wasm_sdk.js";
66

77
const DEFAULT_INDEXER_URL = "http://127.0.0.1:3002";
88
const DEFAULT_TRANSPORT_ENDPOINT = "rpc://127.0.0.1:3000/json-rpc";
9+
const FIXED_LIFECYCLE_MNEMONIC =
10+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
11+
const FIXED_SENDER_MNEMONIC =
12+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
13+
const FIXED_RECEIVER_MNEMONIC =
14+
"legal winner thank year wave sausage worth useful legal winner thank yellow";
915

1016
function log(message, data = undefined) {
1117
const out = document.getElementById("out");
@@ -33,23 +39,18 @@ function readPositiveInt(id, fallback) {
3339
return n;
3440
}
3541

36-
function parseWalletData(id) {
37-
const raw = readText(id);
38-
if (!raw) {
39-
throw new Error(`${id} cannot be empty`);
40-
}
41-
let parsed;
42-
try {
43-
parsed = JSON.parse(raw);
44-
} catch (err) {
45-
throw new Error(`${id} is not valid JSON: ${String(err)}`);
46-
}
47-
return JSON.stringify(parsed);
42+
function senderFundingHint(senderAddress) {
43+
return {
44+
sender_address: senderAddress,
45+
action: "Fund this sender address on regtest and mine >= 1 block (recommended: 6).",
46+
example_amount_btc: 1,
47+
js_hook: "Optionally define window.regtestFund({ address, amountBtc, mineBlocks })",
48+
};
4849
}
4950

50-
function buildWalletDataFromGeneratedKeys(keys, role, seed) {
51+
function buildWalletDataFromGeneratedKeys(keys, role) {
5152
return {
52-
data_dir: `/tmp/rln_wasm_${role}_${seed}`,
53+
data_dir: `/tmp/rln_wasm_${role}_fixed`,
5354
bitcoin_network: "Regtest",
5455
database_type: "Sqlite",
5556
max_allocations_per_utxo: 5,
@@ -62,28 +63,20 @@ function buildWalletDataFromGeneratedKeys(keys, role, seed) {
6263
};
6364
}
6465

65-
function ensureWalletDataInputsFilled() {
66-
const senderWalletInput = document.getElementById("senderWalletData");
67-
const receiverWalletInput = document.getElementById("receiverWalletData");
68-
if (!senderWalletInput || !receiverWalletInput) {
69-
return;
70-
}
71-
if (senderWalletInput.value.trim() && receiverWalletInput.value.trim()) {
72-
return;
73-
}
66+
async function createRlnInstance(role, walletMnemonic, transportEndpoint, lifecycle) {
67+
const keys = rgbRestoreKeysValue("regtest", walletMnemonic);
68+
const walletData = buildWalletDataFromGeneratedKeys(keys, role);
69+
const walletDataJson = JSON.stringify(walletData);
7470

75-
const seed = Date.now().toString();
76-
const senderKeys = rgbGenerateKeysValue("regtest");
77-
const receiverKeys = rgbGenerateKeysValue("regtest");
78-
const senderWalletData = buildWalletDataFromGeneratedKeys(senderKeys, "sender", seed);
79-
const receiverWalletData = buildWalletDataFromGeneratedKeys(receiverKeys, "receiver", seed);
80-
81-
senderWalletInput.value = JSON.stringify(senderWalletData, null, 2);
82-
receiverWalletInput.value = JSON.stringify(receiverWalletData, null, 2);
83-
log("WalletData generated at runtime", {
84-
sender_mnemonic_words: senderKeys.mnemonic.split(" ").length,
85-
receiver_mnemonic_words: receiverKeys.mnemonic.split(" ").length,
86-
});
71+
const sdk = new RlnWasmSdk();
72+
await sdk.initValue(lifecycle.password, lifecycle.mnemonic);
73+
await sdk.unlock(JSON.stringify({ password: lifecycle.password }));
74+
75+
const node = sdk.createNodeHandle(transportEndpoint);
76+
const wallet = await sdk.createWallet(walletDataJson);
77+
node.attachWallet(wallet);
78+
79+
return { sdk, node, wallet };
8780
}
8881

8982
async function signPsbt(unsignedPsbt) {
@@ -131,6 +124,28 @@ async function ensureRgbAllocations(walletHandle, online) {
131124
return after;
132125
}
133126

127+
async function tryAutoFundSender(senderAddress, senderWallet, senderOnline) {
128+
if (typeof window.regtestFund !== "function") {
129+
return false;
130+
}
131+
132+
await window.regtestFund({
133+
address: senderAddress,
134+
amountBtc: 1,
135+
mineBlocks: 6,
136+
});
137+
await senderWallet.syncOnline(senderOnline);
138+
139+
const refreshed = senderWallet.getBtcBalanceValue();
140+
const spendable =
141+
refreshed &&
142+
refreshed.vanilla &&
143+
typeof refreshed.vanilla.spendable === "number"
144+
? refreshed.vanilla.spendable
145+
: 0;
146+
return spendable > 0;
147+
}
148+
134149
function pickInvoiceString(invoiceResponse) {
135150
if (typeof invoiceResponse === "string") return invoiceResponse;
136151
if (invoiceResponse && typeof invoiceResponse.invoice === "string") {
@@ -160,30 +175,37 @@ async function run() {
160175

161176
await init();
162177
log("WASM init", { ok: true });
163-
ensureWalletDataInputsFilled();
164178

165-
const indexerUrl = readText("indexerUrl");
166-
const transportEndpoint = readText("transportEndpoint");
179+
const indexerUrl = DEFAULT_INDEXER_URL;
180+
const transportEndpoint = DEFAULT_TRANSPORT_ENDPOINT;
167181
const issueAmount = readPositiveInt("issueAmount", 1000);
168182
const sendAmount = readPositiveInt("sendAmount", 100);
169-
170-
if (!indexerUrl) {
171-
throw new Error("indexerUrl cannot be empty");
172-
}
173-
if (!transportEndpoint) {
174-
throw new Error("transportEndpoint cannot be empty");
175-
}
176183
if (sendAmount > issueAmount) {
177184
throw new Error("sendAmount cannot be greater than issueAmount");
178185
}
179186

180-
const senderWalletDataJson = parseWalletData("senderWalletData");
181-
const receiverWalletDataJson = parseWalletData("receiverWalletData");
182-
183-
const sdk = new RlnWasmSdk();
184-
const sender = await sdk.createWalletHandleAsync(senderWalletDataJson);
185-
const receiver = await sdk.createWalletHandleAsync(receiverWalletDataJson);
186-
log("Wallet handles created", { ok: true });
187+
const lifecycle = {
188+
password: "rln-fixed-password",
189+
mnemonic: FIXED_LIFECYCLE_MNEMONIC,
190+
};
191+
const senderRln = await createRlnInstance(
192+
"sender",
193+
FIXED_SENDER_MNEMONIC,
194+
transportEndpoint,
195+
lifecycle
196+
);
197+
const receiverRln = await createRlnInstance(
198+
"receiver",
199+
FIXED_RECEIVER_MNEMONIC,
200+
transportEndpoint,
201+
lifecycle
202+
);
203+
const sender = senderRln.wallet;
204+
const receiver = receiverRln.wallet;
205+
const senderNode = senderRln.node;
206+
const receiverNode = receiverRln.node;
207+
log("Two RLN instances initialized and unlocked", { ok: true });
208+
log("Using fixed endpoints", { indexerUrl, transportEndpoint });
187209
log("Wallet addresses", {
188210
sender_address: sender.getAddress(),
189211
receiver_address: receiver.getAddress(),
@@ -199,19 +221,35 @@ async function run() {
199221
await sender.syncOnline(senderOnline);
200222
await receiver.syncOnline(receiverOnline);
201223
log("Initial sync complete", { ok: true });
202-
log("BTC balances before issue", {
224+
const btcBefore = {
203225
sender: sender.getBtcBalanceValue(),
204226
receiver: receiver.getBtcBalanceValue(),
205-
});
227+
};
228+
log("BTC balances before issue", btcBefore);
229+
const senderSpendableBefore =
230+
btcBefore.sender &&
231+
btcBefore.sender.vanilla &&
232+
typeof btcBefore.sender.vanilla.spendable === "number"
233+
? btcBefore.sender.vanilla.spendable
234+
: 0;
235+
if (senderSpendableBefore <= 0) {
236+
const senderAddress = sender.getAddress();
237+
const fundedViaHook = await tryAutoFundSender(senderAddress, sender, senderOnline);
238+
if (!fundedViaHook) {
239+
log("Sender wallet requires regtest funding", senderFundingHint(senderAddress));
240+
throw new Error("Sender spendable BTC is zero. Fund sender wallet and rerun.");
241+
}
242+
log("Sender auto-funded via window.regtestFund", { ok: true });
243+
}
206244
await ensureRgbAllocations(sender, senderOnline);
207245

208246
const issueReq = {
209247
ticker: "TST",
210-
name: "WASM RGB Demo",
248+
name: "WASM RLN Demo",
211249
precision: 0,
212250
amounts: [issueAmount],
213251
};
214-
const issued = sender.issueAssetNiaValue(issueReq);
252+
const issued = senderNode.issueAssetNiaValue(issueReq);
215253
const assetId = issued.asset_id;
216254
log("Asset issued", issued);
217255

@@ -228,6 +266,7 @@ async function run() {
228266
const invoiceObj = new RlnWasmInvoice(invoiceString);
229267
const invoiceData = invoiceObj.invoiceDataValue();
230268
log("Decoded RGB invoice", invoiceData);
269+
log("Receiver node info", receiverNode.nodeInfoValue());
231270

232271
const recipient = {
233272
recipient_id: invoiceData.recipient_id || invoiceData.recipientId,
@@ -275,38 +314,3 @@ if (runBtn) {
275314
});
276315
});
277316
}
278-
279-
const indexerInput = document.getElementById("indexerUrl");
280-
if (indexerInput && !indexerInput.value.trim()) {
281-
indexerInput.value = DEFAULT_INDEXER_URL;
282-
}
283-
284-
const transportInput = document.getElementById("transportEndpoint");
285-
if (transportInput && !transportInput.value.trim()) {
286-
transportInput.value = DEFAULT_TRANSPORT_ENDPOINT;
287-
}
288-
289-
const senderWalletInput = document.getElementById("senderWalletData");
290-
const receiverWalletInput = document.getElementById("receiverWalletData");
291-
if (
292-
senderWalletInput &&
293-
receiverWalletInput &&
294-
!senderWalletInput.value.trim() &&
295-
!receiverWalletInput.value.trim()
296-
) {
297-
// Generate once on page load for convenience.
298-
// A new runtime-generated pair will also be created in run() when fields are empty.
299-
const seed = Date.now().toString();
300-
const senderKeys = rgbGenerateKeysValue("regtest");
301-
const receiverKeys = rgbGenerateKeysValue("regtest");
302-
senderWalletInput.value = JSON.stringify(
303-
buildWalletDataFromGeneratedKeys(senderKeys, "sender", seed),
304-
null,
305-
2
306-
);
307-
receiverWalletInput.value = JSON.stringify(
308-
buildWalletDataFromGeneratedKeys(receiverKeys, "receiver", seed),
309-
null,
310-
2
311-
);
312-
}

bindings/wasm-sdk/examples/wasm-interop/rgb_asset_transfer_flow.html

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,11 @@
4242
<body>
4343
<h1>RLN WASM RGB Asset Transfer Flow</h1>
4444
<p>
45-
Browser PoC for wallet-level RGB transfer: <code>issueAssetNia</code> ->
45+
Browser PoC for two RLN instances (sender + receiver): <code>issueAssetNia</code> ->
4646
<code>blindReceive</code> -> <code>sendBegin</code> -> sign PSBT ->
4747
<code>sendEnd</code>.
4848
</p>
4949

50-
<div class="row">
51-
<label for="indexerUrl">Indexer URL</label>
52-
<input id="indexerUrl" type="text" value="http://127.0.0.1:3002" />
53-
</div>
54-
55-
<div class="row">
56-
<label for="transportEndpoint">RGB transport endpoint</label>
57-
<input
58-
id="transportEndpoint"
59-
type="text"
60-
value="rpc://127.0.0.1:3000/json-rpc"
61-
/>
62-
</div>
63-
6450
<div class="row">
6551
<label for="issueAmount">Issue amount</label>
6652
<input id="issueAmount" type="number" value="1000" />
@@ -71,16 +57,6 @@ <h1>RLN WASM RGB Asset Transfer Flow</h1>
7157
<input id="sendAmount" type="number" value="100" />
7258
</div>
7359

74-
<div class="row">
75-
<label for="senderWalletData">Sender walletData JSON</label>
76-
<textarea id="senderWalletData" placeholder="Leave empty to auto-generate at runtime"></textarea>
77-
</div>
78-
79-
<div class="row">
80-
<label for="receiverWalletData">Receiver walletData JSON</label>
81-
<textarea id="receiverWalletData" placeholder="Leave empty to auto-generate at runtime"></textarea>
82-
</div>
83-
8460
<div class="row">
8561
<button id="run">Run RGB Transfer Flow</button>
8662
</div>

bindings/wasm-sdk/src/ldk_runtime.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,12 +1263,9 @@ pub fn scaffold_ldk_runtime_manager(runtime_key: String) -> Rc<dyn LdkRuntimeMan
12631263
}
12641264

12651265
#[cfg(test)]
1266-
pub fn reset_scaffold_runtime_storage_for_tests() {
1267-
SCAFFOLD_RUNTIME_STORAGE.with(|storage| storage.borrow_mut().clear());
1268-
RUNTIME_SESSION_AUTHORITY_STATE.with(|state| {
1269-
*state.borrow_mut() = RuntimeSessionAuthorityState::default();
1270-
});
1271-
}
1266+
mod test_utils;
1267+
#[cfg(test)]
1268+
pub(crate) use test_utils::reset_scaffold_runtime_storage_for_tests;
12721269

12731270
#[cfg(test)]
12741271
mod tests;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use super::*;
2+
3+
pub fn reset_scaffold_runtime_storage_for_tests() {
4+
SCAFFOLD_RUNTIME_STORAGE.with(|storage| storage.borrow_mut().clear());
5+
RUNTIME_SESSION_AUTHORITY_STATE.with(|state| {
6+
*state.borrow_mut() = RuntimeSessionAuthorityState::default();
7+
});
8+
}

0 commit comments

Comments
 (0)