@ldgr/client 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,21 +8,51 @@ It does not create a hosted wallet, persist a private key, or replace the
8
8
  browser extension. Use `@ldgr/wallet-sdk` when a website needs an explicit
9
9
  user confirmation in the LDGR extension.
10
10
 
11
- `generateLocalWallet()` creates caller-owned key material for a service wallet
12
- and returns its recovery key exactly to the caller. Store it in your own secret
13
- manager or HSM integration; never log it or send it to LDGR.
11
+ ## Service wallets and recovery keys
12
+
13
+ `generateLocalWallet()` creates caller-owned key material for a service wallet.
14
+ Save its recovery key directly to your secret manager exactly once, then use
15
+ `loadLocalWallet()` on service startup to recreate the signer. The recovery key
16
+ is bearer private-key material: never log, commit, pass it in arguments or
17
+ browser storage, send it to LDGR, or use it for a customer wallet.
18
+
19
+ ```ts
20
+ import { generateLocalWallet, loadLocalWallet } from "@ldgr/client";
21
+
22
+ // Provision once. `secretStore` represents your secret-manager or HSM workflow.
23
+ const created = await generateLocalWallet({ ledgerId: 1 });
24
+ await secretStore.writeOnce("ldgr-service-wallet", created.recoveryKey);
25
+
26
+ // On each service startup, recover the same signer without printing the key.
27
+ const serviceWallet = await loadLocalWallet({
28
+ ledgerId: 1,
29
+ recoveryKey: await secretStore.read("ldgr-service-wallet"),
30
+ });
31
+
32
+ console.log(serviceWallet.address); // safe to identify the service wallet
33
+ ```
34
+
35
+ This is for backend/Bun service wallets only. Websites should use
36
+ `@ldgr/wallet-sdk` and an extension-confirmed customer wallet instead.
14
37
 
15
38
  ```ts
16
39
  import { createLdgrClient } from "@ldgr/client";
17
40
 
18
- const client = createLdgrClient({ apiBase: "https://tst.ldgr.ltd" });
41
+ const client = createLdgrClient({ network: "testnet" });
19
42
  const payment = await client.payments.verifyTransfer(transactionHash, {
20
43
  to: merchantAddress,
21
- asset: "dng",
44
+ asset: "t0",
22
45
  amount: "2.50",
23
46
  });
24
47
  ```
25
48
 
49
+ Pass `network: "testnet" | "mainnet"` or the matching ledger id (`0` / `1`).
50
+ The client selects `https://tst.ldgr.ltd` or `https://ldgr.ltd` by default; pass
51
+ `apiBase` alongside `network` for a local deployment or proxy. Use public native
52
+ asset id `t0` on testnet and `t1` on mainnet. Legacy `dng` remains accepted for
53
+ compatibility, while raw authoritative transactions retain their signed wire
54
+ code `dng`.
55
+
26
56
  Only fulfil an order after `payment.status === "confirmed"`, and store the
27
57
  transaction hash as the idempotency key. AMM quotes are estimates: pass an
28
58
  explicit minimum output in the signed swap payload and re-quote immediately
package/dist/index.d.ts CHANGED
@@ -69,6 +69,6 @@ export declare function createLdgrClient(options: LdgrClientOptions): {
69
69
  }>(path: string, signed: SignedSubmission) => Promise<T>;
70
70
  };
71
71
  export { LdgrClientError } from "./types";
72
- export type { LdgrClientOptions, LdgrTransaction, PaymentExpectation, PaymentVerification, SignedSubmission } from "./types";
73
- export { createWebCryptoSigner, generateLocalWallet } from "./signer";
72
+ export type { LdgrClientOptions, LdgrNetwork, LdgrTransaction, PaymentExpectation, PaymentVerification, SignedSubmission } from "./types";
73
+ export { createWebCryptoSigner, generateLocalWallet, loadLocalWallet } from "./signer";
74
74
  export type { LdgrSigner, LocalLdgrWallet } from "./signer";
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // src/client.ts
2
+ import { resolveLdgrNetwork } from "@ldgr/protocol";
3
+
1
4
  // src/types.ts
2
5
  class LdgrClientError extends Error {
3
6
  status;
@@ -17,6 +20,13 @@ function normalizeApiBase(apiBase) {
17
20
  throw new TypeError("apiBase must be an absolute HTTP(S) URL");
18
21
  return normalized;
19
22
  }
23
+ function apiBaseForOptions(options) {
24
+ if (options.apiBase !== undefined)
25
+ return normalizeApiBase(options.apiBase);
26
+ if (options.network === undefined)
27
+ throw new TypeError("apiBase is required when network is not provided");
28
+ return resolveLdgrNetwork(options.network) === 0 ? "https://tst.ldgr.ltd" : "https://ldgr.ltd";
29
+ }
20
30
  function assertApiPath(path) {
21
31
  if (!path.startsWith("/api/v1/") || path.includes("//") || path.includes("..")) {
22
32
  throw new TypeError("path must be an /api/v1/ relative path");
@@ -37,7 +47,7 @@ async function parseJson(response) {
37
47
  return body;
38
48
  }
39
49
  function createTransport(options) {
40
- const apiBase = normalizeApiBase(options.apiBase);
50
+ const apiBase = apiBaseForOptions(options);
41
51
  const request = options.fetch ?? globalThis.fetch;
42
52
  if (typeof request !== "function")
43
53
  throw new TypeError("fetch is required");
@@ -66,12 +76,17 @@ function queryPath(path, query) {
66
76
  const encoded = search.toString();
67
77
  return encoded ? `${path}?${encoded}` : path;
68
78
  }
79
+
80
+ // src/index.ts
81
+ import { resolveLdgrNetwork as resolveLdgrNetwork2, resolvePublicAssetCode } from "@ldgr/protocol";
69
82
  // src/signer.ts
70
83
  import {
71
84
  deriveAddress,
85
+ decodeEd25519RecoveryKey,
72
86
  encodeEd25519RecoveryKey,
73
87
  exportEd25519PublicKey,
74
88
  generateEd25519KeyPair,
89
+ importEd25519PrivateSeed,
75
90
  signCanonicalPayload
76
91
  } from "@ldgr/protocol";
77
92
  function encodeBase64Url(bytes) {
@@ -108,6 +123,11 @@ async function generateLocalWallet(input) {
108
123
  recoveryKey: encodeEd25519RecoveryKey(privateKeyJwk)
109
124
  };
110
125
  }
126
+ async function loadLocalWallet(input) {
127
+ const { privateSeed } = decodeEd25519RecoveryKey(input.recoveryKey);
128
+ const keyPair = await importEd25519PrivateSeed(privateSeed);
129
+ return createWebCryptoSigner({ ...input, ...keyPair });
130
+ }
111
131
 
112
132
  // src/index.ts
113
133
  function normalizedHash(transactionHash) {
@@ -136,6 +156,15 @@ function transactionFields(transaction) {
136
156
  }
137
157
  function createLdgrClient(options) {
138
158
  const transport = createTransport(options);
159
+ const ledgerId = options.network === undefined ? undefined : resolveLdgrNetwork2(options.network);
160
+ const resolveAsset = (asset) => {
161
+ if (ledgerId !== undefined)
162
+ return resolvePublicAssetCode(asset, ledgerId);
163
+ if (/^t[01]$/i.test(asset.trim())) {
164
+ throw new TypeError("network is required when using public native asset ids t0 or t1");
165
+ }
166
+ return asset;
167
+ };
139
168
  return {
140
169
  ledger: {
141
170
  get: () => transport.get("/api/v1/ledger"),
@@ -143,8 +172,8 @@ function createLdgrClient(options) {
143
172
  },
144
173
  accounts: {
145
174
  listBalances: (address) => transport.get(`/api/v1/balances/${encodePathSegment(address)}`),
146
- getBalance: (address, asset) => transport.get(`/api/v1/balances/${encodePathSegment(address)}/${encodePathSegment(asset)}`),
147
- getNextNonce: (address, asset) => transport.get(`/api/v1/nonces/${encodePathSegment(address)}/${encodePathSegment(asset)}`)
175
+ getBalance: (address, asset) => transport.get(`/api/v1/balances/${encodePathSegment(address)}/${encodePathSegment(resolveAsset(asset))}`),
176
+ getNextNonce: (address, asset) => transport.get(`/api/v1/nonces/${encodePathSegment(address)}/${encodePathSegment(resolveAsset(asset))}`)
148
177
  },
149
178
  transactions: {
150
179
  get: (transactionHash) => transport.get(`/api/v1/transactions/${normalizedHash(transactionHash)}`)
@@ -152,9 +181,9 @@ function createLdgrClient(options) {
152
181
  amm: {
153
182
  listPools: () => transport.get("/api/v1/amm/pools"),
154
183
  getPool: (poolId) => transport.get(`/api/v1/amm/pools/${encodePathSegment(poolId)}`),
155
- getPoolByPair: (assetA, assetB) => transport.get(`/api/v1/amm/pools/by-pair/${encodePathSegment(assetA)}/${encodePathSegment(assetB)}`),
156
- quoteSwap: (poolId, assetIn, amountIn) => transport.get(queryPath(`/api/v1/amm/pools/${encodePathSegment(poolId)}/quote`, { assetIn, amountIn })),
157
- quoteRoute: (assetIn, assetOut, amountIn, maxHops) => transport.get(queryPath("/api/v1/amm/routes/quote", { assetIn, assetOut, amountIn, maxHops }))
184
+ getPoolByPair: (assetA, assetB) => transport.get(`/api/v1/amm/pools/by-pair/${encodePathSegment(resolveAsset(assetA))}/${encodePathSegment(resolveAsset(assetB))}`),
185
+ quoteSwap: (poolId, assetIn, amountIn) => transport.get(queryPath(`/api/v1/amm/pools/${encodePathSegment(poolId)}/quote`, { assetIn: resolveAsset(assetIn), amountIn })),
186
+ quoteRoute: (assetIn, assetOut, amountIn, maxHops) => transport.get(queryPath("/api/v1/amm/routes/quote", { assetIn: resolveAsset(assetIn), assetOut: resolveAsset(assetOut), amountIn, maxHops }))
158
187
  },
159
188
  subscriptions: {
160
189
  getPlan: (planId) => transport.get(`/api/v1/subscription-plans/${encodePathSegment(planId)}`),
@@ -189,6 +218,7 @@ function createLdgrClient(options) {
189
218
  payments: {
190
219
  async verifyTransfer(transactionHash, expected) {
191
220
  const hash = normalizedHash(transactionHash);
221
+ const expectedAsset = resolveAsset((expected.asset ?? "dng").trim() || "dng");
192
222
  let transaction;
193
223
  try {
194
224
  transaction = await transport.get(`/api/v1/payments/v0.1/transactions/${hash}`);
@@ -198,7 +228,6 @@ function createLdgrClient(options) {
198
228
  throw error;
199
229
  }
200
230
  const fields = transactionFields(transaction);
201
- const expectedAsset = (expected.asset ?? "dng").trim() || "dng";
202
231
  if (transaction.action !== "transfer" || fields === null || fields.to !== expected.to || fields.asset !== expectedAsset || !sameDecimalAmount(fields.amount, expected.amount) || expected.reference !== undefined && paymentReference(transaction.metadata) !== expected.reference) {
203
232
  return { status: "mismatch", transactionHash: hash };
204
233
  }
@@ -209,6 +238,7 @@ function createLdgrClient(options) {
209
238
  };
210
239
  }
211
240
  export {
241
+ loadLocalWallet,
212
242
  generateLocalWallet,
213
243
  createWebCryptoSigner,
214
244
  createLdgrClient,
package/dist/signer.d.ts CHANGED
@@ -17,3 +17,8 @@ export declare function createWebCryptoSigner(input: {
17
17
  export declare function generateLocalWallet(input: {
18
18
  readonly ledgerId: LedgerId;
19
19
  }): Promise<LocalLdgrWallet>;
20
+ /** Restores a caller-owned signer from canonical LDGR recovery material. */
21
+ export declare function loadLocalWallet(input: {
22
+ readonly recoveryKey: string;
23
+ readonly ledgerId: LedgerId;
24
+ }): Promise<LdgrSigner>;
package/dist/types.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import type { LdgrNetwork } from "@ldgr/protocol";
1
2
  export type LedgerId = 0 | 1;
3
+ export type { LdgrNetwork } from "@ldgr/protocol";
2
4
  export type LdgrTransaction = {
3
5
  readonly action: string;
4
6
  readonly from?: string;
@@ -33,7 +35,10 @@ export type SignedSubmission = {
33
35
  readonly feeSponsor?: unknown;
34
36
  };
35
37
  export type LdgrClientOptions = {
36
- readonly apiBase: string;
38
+ /** `testnet` / `0` or `mainnet` / `1`; required to use public native ids `t0` / `t1`. */
39
+ readonly network?: LdgrNetwork;
40
+ /** Defaults to the canonical public origin when `network` is supplied. */
41
+ readonly apiBase?: string;
37
42
  readonly fetch?: typeof globalThis.fetch;
38
43
  };
39
44
  export type LdgrClientErrorBody = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ldgr/client",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "exports": {
@@ -17,7 +17,7 @@
17
17
  "node": ">=20.19.0"
18
18
  },
19
19
  "dependencies": {
20
- "@ldgr/protocol": "^0.1.0"
20
+ "@ldgr/protocol": "^0.2.0"
21
21
  },
22
22
  "scripts": {
23
23
  "build": "rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm --packages external && tsc -p tsconfig.build.json",