@ldgr/wallet-sdk
v0.6.0
Published
The browser SDK provides one chooser for the Ledra Wallet extension, **Ledra Connect** (the encrypted cross-device Web Wallet QR path), and the existing same-device Web Wallet popup. Ledra Connect sessions are transport only: this package never contains w
Readme
LDGR wallet SDK
The browser SDK provides one chooser for the Ledra Wallet extension, Ledra Connect (the encrypted cross-device Web Wallet QR path), and the existing same-device Web Wallet popup. Ledra Connect sessions are transport only: this package never contains wallet keys and cannot bypass wallet confirmation.
import { createLdgrWallet, type LdgrWalletProvider } from "@ldgr/wallet-sdk";
const provider = (window as Window & { ldgr?: LdgrWalletProvider }).ldgr;
const wallet = createLdgrWallet(provider);
await wallet.connect({ network: "mainnet" }); // or "testnet", 0, or 1
const balance = await wallet.getBalance("t1");
const transaction = await wallet.sendNative({
to: "ldgr1…",
amount: "0.25",
memo: "Payment",
});With the extension, connect() is remembered for the exact website origin until disconnect() is called. Web Wallet transports keep the account only in the current page runtime and ask again after reload. Account and balance reads require that connection. Every supported spend opens wallet approval with the reported website, recipient, amount, and memo. It returns the Worker transaction identity only after confirmation and submission; it never returns a signing key or raw signature.
Ledra Connect
Ledra Connect is the published SDK's reusable, LDGR-native QR connection path for a Web Wallet running on another device. The relay sees only opaque ciphertext. The wallet capability token and a freshly generated 256-bit AES-GCM key stay in the pairing URI fragment, so neither reaches the relay in an HTTP request:
import {
createLdgrWallet,
createRemoteWalletSession,
walletConnectQrDataUrl,
} from "@ldgr/wallet-sdk";
const session = await createRemoteWalletSession({
ledgerId: 1,
relayOrigin: "https://ldgr.ltd",
});
// Render directly into your connection UI. Never log or persist this URI: its
// fragment contains the wallet capability and the session encryption key.
document.querySelector("#wallet-qr").src = await walletConnectQrDataUrl(session.pairingUri);
const wallet = createLdgrWallet(session);
await wallet.connect({ network: "mainnet" });
await wallet.sendNative({ to: "ldgr1…", amount: "0.25" });
await session.disconnect();Each JSON request and response is encrypted in the browser with AES-256-GCM,
a fresh 96-bit IV, and AAD bound to protocol version, session, positive
sequential request ID, and direction. session.request(request, { signal })
supports cancellation; cancelling an in-flight approval disconnects the relay
session. Remote connection transports requests only: Ledra Wallet still shows
the approval and performs signing itself.
Browser widgets use connectWithUnifiedWallet() internally. Its shared modal
offers the injected Ledra Wallet extension when present, Ledra Connect QR
pairing, and the existing same-device Web Wallet popup. Applications may invoke
the same UI directly from @ldgr/wallet-sdk/unified-wallet-connect.
Set api-base="https://tst.ldgr.ltd" on widgets targeting testnet. The
standalone <ldgr-connect-button> also accepts network="testnet" or
network="mainnet"; network takes precedence when both are present, and an
unconfigured merchant-domain widget defaults to mainnet.
Use network: "testnet" | "mainnet" or network: 0 | 1 when connecting.
Public native asset ids are t0 on testnet and t1 on mainnet; pass them to
getBalance, send, swaps, and contract helpers. dng remains a
backwards-compatible input, but the extension receives it only as the internal
wire code. Raw transaction records also retain dng so they remain exact
signed ledger data.
Public object reads do not need a wallet connection:
import { getObject, getObjectByKey } from "@ldgr/wallet-sdk/browser";
const byId = await getObject("o0123456789abcde"); // null if missing
const byKey = await getObjectByKey("myapp.profiles", "user:1");Optional apiBase and injectable fetch match getPaymentStatus. Writes still go through createObject / updateObject / setObjectKeymap after connect().
sendBulkNative({ transfers }) opens one confirmation for a list of native Ledra recipients (max 25). After approval the extension submits a single transfer_batch ledger write and returns { asset, sequence, transactionHash, count }.
watchAsset({ asset, ledgerId? }) and openSwap({ assetIn, assetOut, ledgerId? }) do not require a prior connect. The former opens an add-token confirmation; the latter opens the wallet window on the Swap screen with the pair preselected.
The wallet must be unlocked for spend actions. Unlocked sessions last ten minutes; after expiry, the user unlocks the selected wallet before approving a new action.
Browser payment SDK
Install the package when your site bundles its own browser code:
npm install @ldgr/wallet-sdkThe root entrypoint is safe to use for the typed provider wrapper.
@ldgr/wallet-sdk/browser is browser-only: it requires DOM, WebCrypto, and
custom-element globals to provide the payment widgets and helper functions.
It must not be imported from Node.js or server-side rendering.
For a no-build merchant embed, use the browser module served by the ledger Worker:
<script type="module">
import { getPaymentStatus } from "https://ledger.example/sdk/ldgr.js?v=0.6.0";
document.addEventListener("ldgr-payment-connected", (event) => {
console.log("connected", event.detail.address);
});
document.addEventListener("ldgr-payment-submitted", async (event) => {
const transaction = event.detail;
const status = await getPaymentStatus(transaction.transactionHash, {
to: "ldgr1merchant…",
amount: "2.50",
});
console.log(status);
});
</script>
<ldgr-pay-button
theme="dark"
to="ldgr1merchant…"
amount="2.50"
memo="Order payment"
reference="public-order-reference">
</ldgr-pay-button>Stablecoin / FT example (pass the ledger coin id):
<script type="module">
import { getPaymentStatus } from "https://ledger.example/sdk/ldgr.js?v=0.6.0";
document.addEventListener("ldgr-stable-payment-submitted", async (event) => {
const transaction = event.detail;
const status = await getPaymentStatus(transaction.transactionHash, {
to: "ldgr1merchant…",
amount: "5.00",
asset: "t0f8eaa3828cfa3b",
});
console.log(status);
});
</script>
<ldgr-stable-pay-button
theme="dark"
coin-id="t0f8eaa3828cfa3b"
to="ldgr1merchant…"
amount="5.00"
reference="order-42">
</ldgr-stable-pay-button>When the selected asset is Ledra Dollar and the widget has to, amount,
coin-id, and reference, a disconnected payer can open Ledra Checkout and
fund a locally created browser wallet with a supported external stablecoin.
The submitted event includes checkoutSessionId; getCheckoutStatus() checks
that session against the merchant's immutable payment terms. Browser status is
still only a UX hint — the merchant backend must verify the authoritative
checkout session before fulfilment.
Payment and subscription widgets show only Connect wallet until the wallet is connected, then replace it with the fixed Pay in Wallet or Subscribe action. These action labels are not configurable.
<ldgr-pay-button>, <ldgr-subscribe-button>, <ldgr-stable-pay-button>, and <ldgr-connect-button> render a branded Shadow DOM card (LDGR mark, title, “What is this?” dialog, Connect wallet and/or primary action). They offer the injected extension for same-device use and Ledra Connect for an encrypted Web Wallet QR session; the one-request popup remains a compatibility path. Every path requires explicit approval. Supported Web Wallet actions are connect, native/token payment, subscription_lock_v1, and atomic ammRouteSwap; other provider methods still require the extension. The “What is this?” dialog links to the LDGR website. Use theme="dark" (default) or theme="light"; override --ldgr-widget-* CSS variables on the host for further theming. Pay widgets show the amount with the token display name and icon. Pay/subscribe/stable emit *-connected, *-pending, *-submitted, and *-error events; connect emits ldgr-connect-connected / ldgr-connect-error. payLdgr() / payStablecoin() / subscribeToPlan() still require an already-connected origin when called directly from script. getPaymentStatus() reads the public, CORS-enabled v0.1 transaction endpoint and confirms that the committed transaction is an exact transfer to the expected address, decimal amount, asset, and (when supplied) public reference. Pass { asset: "t0", network: "testnet" } or { asset: "t1", network: "mainnet" } for native verification; omitted asset remains compatible with legacy dng.
Reusable swap widget
The browser SDK also registers <ldgr-swap-widget>, a small Uniswap-style AMM
card for sites that want to configure a pair and let the wallet handle the
confirmation. It fetches a live route quote from the configured ledger and
submits one amm_route_swap confirmation through the existing wallet provider:
<script type="module">
import "https://tst.ldgr.ltd/sdk/ldgr.js?v=0.6.0";
document.addEventListener("ldgr-swap-submitted", (event) => {
console.log("swap submitted", event.detail.transactionHash);
});
</script>
<ldgr-swap-widget
api-base="https://tst.ldgr.ltd"
ledger-id="0"
token-a="t0"
token-b="t0f8eaa3828cfa3b"
allow-search
token-whitelist="t0,t0f8eaa3828cfa3b"
theme="dark"
slippage="0.5">
</ldgr-swap-widget>token-a and token-b are required pair choices (native public ids are
t0/t1); ledger-id must be 0 or 1 and must match the ledger behind
api-base. Search is opt-in with allow-search. token-whitelist is an
optional comma-separated list of public token ids and always limits the picker,
including when search is enabled. Without allow-search, the picker only
offers the configured pair or whitelist. slippage is a decimal percentage
used to calculate the integer-safe amountOutMin sent for wallet approval.
Connecting uses the shared unified chooser, so visitors can approve through
the extension, Ledra Connect QR on another device, or the same-device Web
Wallet path.
Verified and partner tokens use the same compact badge treatment as the
extension and Web Wallet. Official badges are marked Verified; supported
partner tokens are marked Partner. The widget reads these display-only
labels from the explorer asset metadata.
The widget emits ldgr-swap-connected, ldgr-swap-submitted, and
ldgr-swap-error; submitted detail includes the settled transaction identity.
Use the --ldgr-widget-* variables or the exposed ::part(card),
::part(token-button), ::part(quote), and ::part(submit-button) hooks to
customize its presentation.
For fungible / stablecoin payments, use payStablecoin({ coinId, to, amount, memo?, reference? }) or <ldgr-stable-pay-button coin-id="t…" to="…" amount="…">. The coin-id is the ledger token wire code (e.g. testnet Ledra Dollar t0f8eaa3828cfa3b). These helpers use wallet send with that asset and refuse native t0 / t1 / legacy dng (use payLdgr instead). Verify with getPaymentStatus(hash, { to, amount, asset: coinId, reference? }).
LDGR402 HTTP payments
parseLdgr402Requirement, payLdgr402, encodeLdgr402Payment, and
decodeLdgr402Payment support the LDGR-native HTTP 402 Payment Required
flow. A merchant returns a versioned JSON requirement with the exact public
asset id, amount, recipient, challenge, ledger, and expiry. After explicit
wallet approval, payLdgr402 submits an ordinary native or fungible-token
transfer and returns a transaction-hash proof. The client sends
encodeLdgr402Payment(proof) in the LDGR402-Payment header when retrying the
request.
The merchant must persist the challenge and independently verify the returned
hash with @ldgr/client payments.verifyLdgr402 (or the lower-level
payments.verifyTransfer), including the exact recipient, amount, asset, and
challenge as the public reference. Browser events and wallet confirmation are
not fulfillment proof. See
docs/integrations/ldgr402.md.
Browser confirmation is not proof of payment for fulfilment. A merchant server must store the expected order terms, then verify the returned sequence or transaction hash against the authoritative Worker before marking an order paid. Never put a customer identity, secret, or private order detail in reference or transaction metadata: they are public ledger data.
An irreversible small-value burner demonstration is available on testnet Playground at https://tst.ldgr.ltd/playground/burn/. It is only for testing the wallet flow: connect the wallet, then send 0.001 Ledra Testnet to the permanent burner address. That transfer cannot be undone.
