@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 +35 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +37 -7
- package/dist/signer.d.ts +5 -0
- package/dist/types.d.ts +6 -1
- package/package.json +2 -2
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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({
|
|
41
|
+
const client = createLdgrClient({ network: "testnet" });
|
|
19
42
|
const payment = await client.payments.verifyTransfer(transactionHash, {
|
|
20
43
|
to: merchantAddress,
|
|
21
|
-
asset: "
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|