mirror of
https://github.com/aljazceru/breez-sdk-docs.git
synced 2025-12-18 14:24:19 +01:00
@@ -6,6 +6,7 @@ src = "src"
|
|||||||
title = "Breez SDK"
|
title = "Breez SDK"
|
||||||
|
|
||||||
[output.html]
|
[output.html]
|
||||||
|
additional-js = ["tabs.js"]
|
||||||
git-repository-url = "https://github.com/breez/breez-sdk-docs"
|
git-repository-url = "https://github.com/breez/breez-sdk-docs"
|
||||||
edit-url-template = "https://github.com/breez/breez-sdk-docs/edit/main/{path}"
|
edit-url-template = "https://github.com/breez/breez-sdk-docs/edit/main/{path}"
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ Connecting to a node requires a seed (your master key) and credentials. The seed
|
|||||||
|
|
||||||
Breez SDK is available in several platforms. Follow the [Installing](install.md) page for instructions on how to install on your platform.
|
Breez SDK is available in several platforms. Follow the [Installing](install.md) page for instructions on how to install on your platform.
|
||||||
|
|
||||||
|
<custom-tabs category="lang">
|
||||||
|
<div slot="title">Rust</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
The first step is to register a new node. In order to do that a seed is needed.
|
||||||
## Registering a new node
|
## Registering a new node
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
let seed = <your seed>;
|
let seed = <your seed>;
|
||||||
@@ -62,4 +67,138 @@ if let Some(node_state) = sdk.node_info()? {
|
|||||||
let balance_onchain = node_state.onchain_balance_msat;
|
let balance_onchain = node_state.onchain_balance_msat;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</section>
|
||||||
|
<div slot="title">Swift</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
The first step is to register a new node
|
||||||
|
## Registering a new node
|
||||||
|
```swift
|
||||||
|
do {
|
||||||
|
let seed = try mnemonicToSeed(phrase: "<mnemonics words>")
|
||||||
|
let inviteCode = ""
|
||||||
|
|
||||||
|
// register_node takes either greenlight credentials (certifate & key) or invite code.
|
||||||
|
// At this example we are using the invite code option.
|
||||||
|
let credentials = try registerNode(network: Network.bitcoin, seed: seed, registerCredentials: nil, inviteCode: inviteCode)
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recovering an existing node
|
||||||
|
```swift
|
||||||
|
do {
|
||||||
|
let seed = try mnemonicToSeed(phrase: "<mnemonics words>")
|
||||||
|
let credentials = try recoverNode(network: Network.bitcoin, seed: seed)
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the credentials are retrieved they should be saved in a secured storage.
|
||||||
|
The next step is to initialize the SDK and start the node:
|
||||||
|
|
||||||
|
## Initializing the SDK
|
||||||
|
```swift
|
||||||
|
|
||||||
|
// SDK events listener
|
||||||
|
class SDKListener: EventListener {
|
||||||
|
func onEvent(e: BreezEvent) {
|
||||||
|
print("received event ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the default config
|
||||||
|
var config = defaultConfig(envType: EnvironmentType.production)
|
||||||
|
|
||||||
|
// Customize the config object according to your needs
|
||||||
|
config.apiKey = "your API key";
|
||||||
|
config.workingDir = "path to an existing directory";
|
||||||
|
|
||||||
|
do {
|
||||||
|
let sdk = try initServices(config: config, seed: seed, creds: credentials, listener: SDKListener());
|
||||||
|
try sdk.start();
|
||||||
|
} catch{
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
At any point we can fetch our balance from the Greenlight node:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
do {
|
||||||
|
let nodeInfo = try sdk.nodeInfo()
|
||||||
|
let lnBalance = nodeInfo?.channelsBalanceMsat
|
||||||
|
let onchainBalance = nodeInfo?.onchainBalanceMsat
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<div slot="title">React Native</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
The first step is to register a new node
|
||||||
|
## Registering a new node
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const seed = await mnemonicToSeed("<mnemonics words>");
|
||||||
|
const invite_code = "<your greenlight invite code>";
|
||||||
|
|
||||||
|
// register_node takes either greenlight credentials (certifate & key) or invite code.
|
||||||
|
// At this example we are using the invite code option.
|
||||||
|
const credentials = await registerNode(Network.BITCOIN, seed, inviteCode);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recovering an existing node
|
||||||
|
```typescript
|
||||||
|
const seed = await mnemonicToSeed("<mnemonics words>");
|
||||||
|
const credentials = await recoverNode(Network.BITCOIN, seed);
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the credentials are retrieved they should be saved in a secured storage.
|
||||||
|
The next step is to initialize the SDK and start the node:
|
||||||
|
|
||||||
|
## Initializing the SDK
|
||||||
|
```typescript
|
||||||
|
|
||||||
|
// SDK events listener
|
||||||
|
addEventListener((type, data) => {
|
||||||
|
console.log(`received event ${type}`);
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create the default config
|
||||||
|
let config = defaultConfig(EnvironmentType.PRODUCTION)
|
||||||
|
|
||||||
|
// Customize the config object according to your needs
|
||||||
|
config.apiKey = "your API key";
|
||||||
|
config.workingDir = "path to an existing directory";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sdkServices = await initServices(config, credentials.deviceKey, credentials.deviceCert, seed);
|
||||||
|
await start();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
At any point we can fetch our balance from the Greenlight node:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const nodeInfo = await nodeInfo();
|
||||||
|
const lnBalance = nodeInfo.channelsBalanceMsat;
|
||||||
|
const onchainBalance = nodeInfo.onchainBalanceMsat;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
</custom-tabs>
|
||||||
|
|
||||||
You are now ready to receive a Lightning [payment](payments.md).
|
You are now ready to receive a Lightning [payment](payments.md).
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# LNURL-Auth
|
# LNURL-Auth
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
<custom-tabs category="lang">
|
||||||
|
<div slot="title">Rust</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
// Endpoint can also be of the form:
|
// Endpoint can also be of the form:
|
||||||
@@ -21,7 +24,57 @@ if let Ok(LnUrlAuth{data: ad}) = parse(lnurl_auth_url).await {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</section>
|
||||||
|
<div slot="title">Swift</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Endpoint can also be of the form:
|
||||||
|
// keyauth://domain.com/auth?key=val
|
||||||
|
let lnurlAuthUrl = "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttvdankjm3lw3skw0tvdankjm3xdvcn6vtp8q6n2dfsx5mrjwtrxdjnqvtzv56rzcnyv3jrxv3sxqmkyenrvv6kve3exv6nqdtyv43nqcmzvdsnvdrzx33rsenxx5unqc3cxgeqgntfgu"
|
||||||
|
|
||||||
|
do {
|
||||||
|
let inputType = try parseInput(s: lnurlAuthUrl)
|
||||||
|
if case .lnUrlAuth(let data) = inputType {
|
||||||
|
let result = try sdk.lnurlAuth(reqData: data)
|
||||||
|
switch result {
|
||||||
|
case .ok:
|
||||||
|
print("Successfully authenticated")
|
||||||
|
case .errorStatus(let data):
|
||||||
|
print("Failed to authenticate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<div slot="title">React Native</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Endpoint can also be of the form:
|
||||||
|
// keyauth://domain.com/auth?key=val
|
||||||
|
let lnurlAuthUrl = "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttvdankjm3lw3skw0tvdankjm3xdvcn6vtp8q6n2dfsx5mrjwtrxdjnqvtzv56rzcnyv3jrxv3sxqmkyenrvv6kve3exv6nqdtyv43nqcmzvdsnvdrzx33rsenxx5unqc3cxgeqgntfgu";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const input = await parseInput(lnurlAuthUrl)
|
||||||
|
if (input.type === InputType.LNURL_AUTH) {
|
||||||
|
const result = await lnurlAuth(input.data)
|
||||||
|
if (result.status === "ok") {
|
||||||
|
print("Successfully authenticated")
|
||||||
|
} else {
|
||||||
|
print("Failed to authenticate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</custom-tab>
|
||||||
|
|
||||||
## Supported Specs
|
## Supported Specs
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
<custom-tabs category="lang">
|
||||||
|
<div slot="title">Rust</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
// Endpoint can also be of the form:
|
// Endpoint can also be of the form:
|
||||||
// lnurlp://domain.com/lnurl-pay?key=val
|
// lnurlp://domain.com/lnurl-pay?key=val
|
||||||
@@ -17,6 +21,47 @@ if let Ok(LnUrlPay{data: pd}) = parse(lnurl_pay_url).await {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<div slot="title">Swift</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Endpoint can also be of the form:
|
||||||
|
// lnurlp://domain.com/lnurl-pay?key=val
|
||||||
|
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
|
||||||
|
let lnurlPayUrl = "lightning@address.com";
|
||||||
|
do {
|
||||||
|
let inputType = try parseInput(s: lnurlPayUrl)
|
||||||
|
if case .lnUrlPay(let data) = inputType {
|
||||||
|
let amountSats = data.minSendable;
|
||||||
|
try sdk.payLnurl(reqData: data, amountSats: amountSats, comment: "comment")
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
<div slot="title">React Native</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Endpoint can also be of the form:
|
||||||
|
// lnurlp://domain.com/lnurl-pay?key=val
|
||||||
|
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
|
||||||
|
let lnurlPayUrl = "lightning@address.com";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const input = await parseInput(lnurlAuthUrl)
|
||||||
|
if (input.type === InputType.LNURL_PAY) {
|
||||||
|
const amountSats = input.minSendable;
|
||||||
|
const result = await payLnurl(input.data, amountSats, "comment")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
</custom-tab>
|
||||||
|
|
||||||
## Supported Specs
|
## Supported Specs
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
<custom-tabs category="lang">
|
||||||
|
<div slot="title">Rust</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
// Endpoint can also be of the form:
|
// Endpoint can also be of the form:
|
||||||
// lnurlw://domain.com/lnurl-withdraw?key=val
|
// lnurlw://domain.com/lnurl-withdraw?key=val
|
||||||
@@ -16,7 +20,48 @@ if let Ok(LnUrlWithdraw{data: wd}) = parse(lnurl_withdraw_url).await {
|
|||||||
sdk.lnurl_withdraw(wd, amount_msat, Some(description)).await?;
|
sdk.lnurl_withdraw(wd, amount_msat, Some(description)).await?;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</section>
|
||||||
|
<div slot="title">Swift</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Endpoint can also be of the form:
|
||||||
|
// lnurlw://domain.com/lnurl-withdraw?key=val
|
||||||
|
let lnurlWithdrawUrl = "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4exctthd96xserjv9mn7um9wdekjmmw843xxwpexdnxzen9vgunsvfexq6rvdecx93rgdmyxcuxverrvcursenpxvukzv3c8qunsdecx33nzwpnvg6ryc3hv93nzvecxgcxgwp3h33lxk"
|
||||||
|
|
||||||
|
do {
|
||||||
|
let inputType = try parseInput(s: lnurlWithdrawUrl)
|
||||||
|
if case .lnUrlWithdraw(let data) = inputType {
|
||||||
|
let amountSat = data.minWithdrawable
|
||||||
|
let description = "Test withdraw"
|
||||||
|
try sdk.withdrawLnurl(reqData: data, amountSats: amountSat, description: "comment")
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
<div slot="title">React Native</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Endpoint can also be of the form:
|
||||||
|
// lnurlw://domain.com/lnurl-withdraw?key=val
|
||||||
|
let lnurlWithdrawUrl = "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4exctthd96xserjv9mn7um9wdekjmmw843xxwpexdnxzen9vgunsvfexq6rvdecx93rgdmyxcuxverrvcursenpxvukzv3c8qunsdecx33nzwpnvg6ryc3hv93nzvecxgcxgwp3h33lxk";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const input = await parseInput(lnurlAuthUrl)
|
||||||
|
if (input.type === InputType.LNURL_WITHDRAW) {
|
||||||
|
const amountSats = input.minWithdrawable;
|
||||||
|
const result = await withdrawLnurl(input.data, amountSats, "comment")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
</custom-tab>
|
||||||
|
|
||||||
## Supported Specs
|
## Supported Specs
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# Sending and receiving Lightning payments
|
# Sending and receiving Lightning payments
|
||||||
|
|
||||||
|
<custom-tabs category="lang">
|
||||||
|
<div slot="title">Rust</div>
|
||||||
|
<section>
|
||||||
## Receiving Lightning Payments
|
## Receiving Lightning Payments
|
||||||
Breez SDK doesn't require you to open a channel and set up your inbound liquidity.
|
Breez SDK doesn't require you to open a channel and set up your inbound liquidity.
|
||||||
Breez SDK automatically connects your node to the LSP peer and you can now receive payments:
|
Breez SDK automatically connects your node to the LSP peer and you can now receive payments:
|
||||||
@@ -20,3 +23,73 @@ let node_id = "...";
|
|||||||
sdk.send_payment(node_id.into(), Some(3000)).await?;
|
sdk.send_payment(node_id.into(), Some(3000)).await?;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<div slot="title">Swift</div>
|
||||||
|
<section>
|
||||||
|
## Receiving Lightning Payments
|
||||||
|
Breez SDK doesn't require you to open a channel and set up your inbound liquidity.
|
||||||
|
Breez SDK automatically connects your node to the LSP peer and you can now receive payments:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
do {
|
||||||
|
let invoice = try sdk.receivePayment(amountSats: 3000, description: "Invoice for 3000 sats")
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sending Lightning Payments
|
||||||
|
```swift
|
||||||
|
let bolt11 = "...";
|
||||||
|
do {
|
||||||
|
let payment = try sdk.sendPayment(bolt11: bolt11, amountSats: 3000)
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sending Spontaneous Lightning Payments
|
||||||
|
```swift
|
||||||
|
let nodeId = "...";
|
||||||
|
do {
|
||||||
|
let payment = try sdk.sendSpontaneousPayment(nodeId: nodeId, amountSats: 3000)
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
<div slot="title">React Native</div>
|
||||||
|
<section>
|
||||||
|
## Receiving Lightning Payments
|
||||||
|
Breez SDK doesn't require you to open a channel and set up your inbound liquidity.
|
||||||
|
Breez SDK automatically connects your node to the LSP peer and you can now receive payments:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const invoice = await receivePayment(3000, "Invoice for 3000 sats")
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sending Lightning Payments
|
||||||
|
```typescript
|
||||||
|
const bolt11 = "...";
|
||||||
|
try {
|
||||||
|
const payment = await sendPayment(bolt11, 3000)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sending Spontaneous Lightning Payments
|
||||||
|
```typescript
|
||||||
|
const nodeId = "...";
|
||||||
|
try {
|
||||||
|
const payment = await sendSpontaneousPayment(nodeId, 3000)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
</custom-tabs>
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
# Receiving an on-chain transaction (swap-in)
|
# Receiving an on-chain transaction (swap-in)
|
||||||
There are cases when you have funds in some bitcoin address and you would like to send those to your lightning node.
|
There are cases when you have funds in some bitcoin address and you would like to send those to your lightning node.
|
||||||
|
|
||||||
|
<custom-tabs category="lang">
|
||||||
|
<div slot="title">Rust</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
let swap_info = sdk.receive_onchain().await?;
|
let swap_info = sdk.receive_onchain().await?;
|
||||||
|
|
||||||
// Send your funds to the bellow bitcoin address
|
// Send your funds to the below bitcoin address
|
||||||
let address = swap_info.bitcoin_address;
|
let address = swap_info.bitcoin_address;
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -29,6 +33,114 @@ Once you have a refundable swap in hand, use the follwing code to execute a refu
|
|||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
let destination_address = "...".into()
|
let destination_address = "...".into()
|
||||||
let sat_per_byte = <efund tx fee rate>
|
let sat_per_vbyte = <refund tx fee rate>
|
||||||
sdk.refund(refundable.bitcoin_address, destination_address, sat_per_byte).await?
|
sdk.refund(refundable.bitcoin_address, destination_address, sat_per_vbyte).await?
|
||||||
```
|
```
|
||||||
|
</section>
|
||||||
|
<div slot="title">Swift</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
```swift
|
||||||
|
do {
|
||||||
|
let swapInfo = try sdk.receiveOnchain()
|
||||||
|
|
||||||
|
// Send your funds to the bellow bitcoin address
|
||||||
|
let address = swapInfo.bitcoinAddress;
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you've sent the funds to the above address, the SDK will monitor this address for unspent confirmed outputs and use a trustless submarine swap to receive these into your Lightning node. You can always monitor the status of the current in-progress swap using the following code:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
do {
|
||||||
|
let swapInfo = try sdk.inProgressSwap()
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The process of receiving funds via an on-chain address is trustless and uses a submarine swap. This means there are two ways to spend the sent funds:
|
||||||
|
|
||||||
|
1. Either by a preimage that is exposed when the Lightning payment is completed - this is the positive case where the swap was successful.
|
||||||
|
2. Or by your node when the swap didn't complete within a certain timeout - this is the negative case where your node will execute a refund.
|
||||||
|
|
||||||
|
In order to execute a refund, you need to supply an on-chain address to where the refunded amount will be sent. The following code will retrieve the refundable swaps:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
do {
|
||||||
|
let refundables = try sdk.listRefundables()
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you have a refundable swap in hand, use the follwing code to execute a refund:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let destinationAddress = "..."
|
||||||
|
let satPerVbyte = <refund tx fee rate>
|
||||||
|
|
||||||
|
do {
|
||||||
|
try sdk.refund(
|
||||||
|
swapAddress: "",
|
||||||
|
toAddress: destinationAddress,
|
||||||
|
satPerVbyte: satPerVbyte)
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
<div slot="title">React Native</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const swapInfo = await receiveOnchain();
|
||||||
|
|
||||||
|
// Send your funds to the below bitcoin address
|
||||||
|
const address = swapInfo.bitcoinAddress;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you've sent the funds to the above address, the SDK will monitor this address for unspent confirmed outputs and use a trustless submarine swap to receive these into your Lightning node. You can always monitor the status of the current in-progress swap using the following code:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const swapInfo = await inProgressSwap()
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The process of receiving funds via an on-chain address is trustless and uses a submarine swap. This means there are two ways to spend the sent funds:
|
||||||
|
|
||||||
|
1. Either by a preimage that is exposed when the Lightning payment is completed - this is the positive case where the swap was successful.
|
||||||
|
2. Or by your node when the swap didn't complete within a certain timeout - this is the negative case where your node will execute a refund.
|
||||||
|
|
||||||
|
In order to execute a refund, you need to supply an on-chain address to where the refunded amount will be sent. The following code will retrieve the refundable swaps:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const refundables = await listRefundables()
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you have a refundable swap in hand, use the follwing code to execute a refund:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const destinationAddress = "..."
|
||||||
|
const satPerVbyte = <refund tx fee rate>
|
||||||
|
try {
|
||||||
|
const result = await refund(refundable.bitcoinAddress, destinationAddress, satPerVbyte)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
</custom-tabs>
|
||||||
@@ -4,6 +4,10 @@ You can send funds from the Breez SDK wallet to an on-chain address as follows.
|
|||||||
|
|
||||||
First, fetch the current reverse swap fees:
|
First, fetch the current reverse swap fees:
|
||||||
|
|
||||||
|
<custom-tabs category="lang">
|
||||||
|
<div slot="title">Rust</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
let current_fees = sdk.fetch_reverse_swap_fees().await?;
|
let current_fees = sdk.fetch_reverse_swap_fees().await?;
|
||||||
|
|
||||||
@@ -43,7 +47,115 @@ for rs in sdk.in_progress_reverse_swaps().await? {
|
|||||||
info!("Reverse swap {} in progress, status is {}", rs.id, rs.breez_status);
|
info!("Reverse swap {} in progress, status is {}", rs.id, rs.breez_status);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</section>
|
||||||
|
<div slot="title">Swift</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
```swift
|
||||||
|
try {
|
||||||
|
let currentFees = try sdk.fetchReverseSwapFees()
|
||||||
|
|
||||||
|
println("Percentage fee for the reverse swap service: \(currentFees.feesPercentage)")
|
||||||
|
println("Estimated miner fees in sats for locking up funds: \(currentFees.feesLockup)")
|
||||||
|
println("Estimated miner fees in sats for claiming funds: \(currentFees.feesClaim)")
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The reverse swap will involve two on-chain transactions, for which the mining fees can only be estimated. They will happen
|
||||||
|
automatically once the process is started, but the last two values above are these estimates to help you get a picture
|
||||||
|
of the total costs.
|
||||||
|
|
||||||
|
Fetching the fees also tells you what is the range of amounts you can send:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
println("Minimum amount, in sats: \(current_fees.min)")
|
||||||
|
println("Maximum amount, in sats: \(current_fees.max)")
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you checked the fees are acceptable, you can start the reverse swap:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let destinationAddress = "bc1.."
|
||||||
|
let amountSat = currentFees.min
|
||||||
|
let satPerVbyte = <fee rate>
|
||||||
|
try {
|
||||||
|
try sdk.sendOnchain(amountSat: amountSat, onchainRecipientAddress: destinationAddress, pairHash: currentFees.feesHash, satPerVbyte: satPerVbyte)
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Starting the reverse swap will trigger a HODL invoice payment, which will only be settled if the entire swap completes.
|
||||||
|
This means you will see an outgoing pending payment in your list of payments, which locks those funds until the invoice
|
||||||
|
is either settled or cancelled. This will happen automatically at the end of the reverse swap.
|
||||||
|
|
||||||
|
You can check its status with:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
for rs in sdk.inProgressReverseSwaps() {
|
||||||
|
println("Reverse swap \(rs.id) in progress, status is \(rs.breezStatus)")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
<div slot="title">React Native</div>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const currentFees = await fetchReverseSwapFees()
|
||||||
|
|
||||||
|
console.log(`Percentage fee for the reverse swap service: ${currentFees.feesPercentage}`);
|
||||||
|
console.log(`Estimated miner fees in sats for locking up funds: ${currentFees.feesLockup}`);
|
||||||
|
console.log(`Estimated miner fees in sats for claiming funds: ${currentFees.feesClaim}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The reverse swap will involve two on-chain transactions, for which the mining fees can only be estimated. They will happen
|
||||||
|
automatically once the process is started, but the last two values above are these estimates to help you get a picture
|
||||||
|
of the total costs.
|
||||||
|
|
||||||
|
Fetching the fees also tells you what is the range of amounts you can send:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
console.log(`Minimum amount, in sats: ${current_fees.min}`);
|
||||||
|
console.log(`Maximum amount, in sats: ${current_fees.max}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you checked the fees are acceptable, you can start the reverse swap:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const destinationAddress = "bc1..";
|
||||||
|
const amountSat = currentFees.min;
|
||||||
|
const satPerVbyte = <fee rate>
|
||||||
|
try {
|
||||||
|
const reverseSwapInfo = sendOnchain(amountSat, destinationAddress, currentFees.feesHash, satPerVbyte)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Starting the reverse swap will trigger a HODL invoice payment, which will only be settled if the entire swap completes.
|
||||||
|
This means you will see an outgoing pending payment in your list of payments, which locks those funds until the invoice
|
||||||
|
is either settled or cancelled. This will happen automatically at the end of the reverse swap.
|
||||||
|
|
||||||
|
You can check its status with:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const swaps = await inProgressReverseSwaps()
|
||||||
|
for (const swap in swaps) {
|
||||||
|
println(`Reverse swap ${swap.id} in progress, status is ${swap.breezStatus}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</section>
|
||||||
|
</custom-tabs>
|
||||||
If the reverse swap is successful, you'll get the on-chain payment on your destination address and the HODL invoice will
|
If the reverse swap is successful, you'll get the on-chain payment on your destination address and the HODL invoice will
|
||||||
change from pending to settled.
|
change from pending to settled.
|
||||||
|
|
||||||
|
|||||||
147
tabs.js
Normal file
147
tabs.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
let selected_ = null;
|
||||||
|
|
||||||
|
customElements.define('custom-tabs', class extends HTMLElement {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(); // always call super() first in the ctor.
|
||||||
|
|
||||||
|
// Create shadow DOM for the component.
|
||||||
|
let shadowRoot = this.attachShadow({ mode: 'open' });
|
||||||
|
shadowRoot.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: inline-block;
|
||||||
|
contain: content;
|
||||||
|
border: 1px solid var(--quote-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#panels {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
#tabs {
|
||||||
|
border-bottom: 1px solid var(--quote-border);
|
||||||
|
background-color: var(--sidebar-bg);
|
||||||
|
}
|
||||||
|
#tabs slot {
|
||||||
|
display: inline-flex; /* Safari bug. Treats <slot> as a parent */
|
||||||
|
}
|
||||||
|
#tabs ::slotted(*) {
|
||||||
|
color: var(--sidebar-fg);
|
||||||
|
padding: 16px 8px;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
border: none; /* if the user users a <button> */
|
||||||
|
}
|
||||||
|
#tabs ::slotted([tabindex="0"]), #tabs ::slotted(*:hover) {
|
||||||
|
color: var(--sidebar-active);
|
||||||
|
}
|
||||||
|
#panels ::slotted([aria-hidden="true"]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="tabs">
|
||||||
|
<slot id="tabsSlot" name="title"></slot>
|
||||||
|
</div>
|
||||||
|
<div id="panels">
|
||||||
|
<slot id="panelsSlot"></slot>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get selected() {
|
||||||
|
return selected_;
|
||||||
|
}
|
||||||
|
|
||||||
|
set selected(idx) {
|
||||||
|
selected_ = idx;
|
||||||
|
this._selectTab(idx);
|
||||||
|
this.setAttribute('selected', idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.setAttribute('role', 'tablist');
|
||||||
|
|
||||||
|
const tabsSlot = this.shadowRoot.querySelector('#tabsSlot');
|
||||||
|
const panelsSlot = this.shadowRoot.querySelector('#panelsSlot');
|
||||||
|
|
||||||
|
this.tabs = tabsSlot.assignedNodes({ flatten: true });
|
||||||
|
this.panels = panelsSlot.assignedNodes({ flatten: true }).filter(el => {
|
||||||
|
return el.nodeType === Node.ELEMENT_NODE;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save refer to we can remove listeners later.
|
||||||
|
this._boundOnTitleClick = this._onTitleClick.bind(this);
|
||||||
|
|
||||||
|
tabsSlot.addEventListener('click', this._boundOnTitleClick);
|
||||||
|
|
||||||
|
this.selected = this._findFirstSelectedTab() || this._findStoredSelectedTab() || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
const tabsSlot = this.shadowRoot.querySelector('#tabsSlot');
|
||||||
|
tabsSlot.removeEventListener('click', this._boundOnTitleClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onTitleClick(e) {
|
||||||
|
if (e.target.slot === 'title') {
|
||||||
|
this.selected = this.tabs.indexOf(e.target);
|
||||||
|
e.target.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_findFirstSelectedTab() {
|
||||||
|
let selectedIdx;
|
||||||
|
for (let [i, tab] of this.tabs.entries()) {
|
||||||
|
tab.setAttribute('role', 'tab');
|
||||||
|
if (tab.hasAttribute('selected')) {
|
||||||
|
selectedIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
_findStoredSelectedTab() {
|
||||||
|
let selectedIdx;
|
||||||
|
if (this.getAttribute("category")) {
|
||||||
|
let selectedText;
|
||||||
|
try { selectedText = localStorage.getItem('mdbook-tabs-' + this.getAttribute("category")); } catch (e) { }
|
||||||
|
if (selectedText) {
|
||||||
|
for (let [i, tab] of this.tabs.entries()) {
|
||||||
|
if (tab.textContent === selectedText) {
|
||||||
|
selectedIdx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectTab(idx = null) {
|
||||||
|
for (let i = 0, tab; tab = this.tabs[i]; ++i) {
|
||||||
|
let select = i === idx;
|
||||||
|
tab.setAttribute('tabindex', select ? 0 : -1);
|
||||||
|
tab.setAttribute('aria-selected', select);
|
||||||
|
this.panels[i].setAttribute('aria-hidden', !select);
|
||||||
|
if (select && this.getAttribute("category") && tab.textContent) {
|
||||||
|
try { localStorage.setItem('mdbook-tabs-' + this.getAttribute("category"), tab.textContent); } catch (e) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user