@ldgr/client 0.2.0 → 0.3.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
@@ -57,3 +57,24 @@ Only fulfil an order after `payment.status === "confirmed"`, and store the
57
57
  transaction hash as the idempotency key. AMM quotes are estimates: pass an
58
58
  explicit minimum output in the signed swap payload and re-quote immediately
59
59
  before signing.
60
+
61
+ ## LDGR402 merchant verification
62
+
63
+ For a persisted LDGR402 requirement and the browser's
64
+ `LDGR402-Payment` header, use the protocol-aware verifier:
65
+
66
+ ```ts
67
+ const verification = await client.payments.verifyLdgr402(
68
+ request.headers.get("LDGR402-Payment") ?? "",
69
+ expectedRequirement,
70
+ );
71
+
72
+ if (verification.status === "confirmed") {
73
+ // Atomically fulfill the challenge using verification.transactionHash.
74
+ }
75
+ ```
76
+
77
+ `verifyLdgr402` checks the proof's ledger and challenge, rejects expired
78
+ requirements, and verifies the exact settled transfer through the
79
+ authoritative Worker. The merchant still owns challenge persistence and
80
+ idempotent fulfillment.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { type LdgrClientOptions, type LdgrTransaction, type PaymentExpectation, type PaymentVerification, type SignedSubmission } from "./types";
1
+ import { type Ldgr402Requirement, type LedgerId } from "@ldgr/protocol";
2
+ import { type LdgrAccountTransactionPage, type LdgrClientOptions, type LdgrAsset, type Ldgr402Verification, type LdgrTransaction, type PaymentExpectation, type PaymentVerification, type SignedSubmission } from "./types";
2
3
  export declare function createLdgrClient(options: LdgrClientOptions): {
3
4
  ledger: {
4
5
  get: () => Promise<{
@@ -24,13 +25,38 @@ export declare function createLdgrClient(options: LdgrClientOptions): {
24
25
  readonly asset: string;
25
26
  readonly nextNonce: string;
26
27
  }>;
28
+ getWalletPublicKey: (address: string) => Promise<{
29
+ readonly address: string;
30
+ readonly publicKey: string;
31
+ }>;
32
+ registerWalletPublicKey: (address: string, registration: {
33
+ readonly version: 1;
34
+ readonly action: "wallet_key_register";
35
+ readonly chain: "ldgr-v1";
36
+ readonly ledgerId: LedgerId;
37
+ readonly from: string;
38
+ readonly publicKey: string;
39
+ readonly timestamp: string;
40
+ readonly signature: string;
41
+ }) => Promise<{
42
+ readonly address: string;
43
+ readonly publicKey: string;
44
+ }>;
45
+ listTransactions(address: string, options?: {
46
+ readonly before?: string;
47
+ readonly asset?: string;
48
+ }): Promise<LdgrAccountTransactionPage>;
49
+ };
50
+ assets: {
51
+ get: (asset: string) => Promise<LdgrAsset>;
27
52
  };
28
53
  transactions: {
29
54
  get: (transactionHash: string) => Promise<LdgrTransaction>;
30
55
  };
31
56
  amm: {
32
- listPools: () => Promise<{
57
+ listPools: (cursor?: string, limit?: number) => Promise<{
33
58
  readonly pools: readonly Record<string, unknown>[];
59
+ readonly nextCursor?: string;
34
60
  }>;
35
61
  getPool: (poolId: string) => Promise<Record<string, unknown>>;
36
62
  getPoolByPair: (assetA: string, assetB: string) => Promise<Record<string, unknown>>;
@@ -62,13 +88,17 @@ export declare function createLdgrClient(options: LdgrClientOptions): {
62
88
  }) => Promise<Record<string, unknown>>;
63
89
  };
64
90
  payments: {
65
- verifyTransfer(transactionHash: string, expected: PaymentExpectation): Promise<PaymentVerification>;
91
+ verifyTransfer: (transactionHash: string, expected: PaymentExpectation) => Promise<PaymentVerification>;
92
+ verifyLdgr402: (proofHeader: string, requirementInput: Ldgr402Requirement, options?: {
93
+ readonly nowMs?: number;
94
+ }) => Promise<Ldgr402Verification>;
66
95
  };
67
96
  submitSigned: <T extends {
68
97
  readonly transactionHash?: string;
69
98
  }>(path: string, signed: SignedSubmission) => Promise<T>;
70
99
  };
71
100
  export { LdgrClientError } from "./types";
72
- export type { LdgrClientOptions, LdgrNetwork, LdgrTransaction, PaymentExpectation, PaymentVerification, SignedSubmission } from "./types";
101
+ export { decodeMerchantLdgr402Payment, parseMerchantLdgr402Requirement } from "./ldgr402";
102
+ export type { LdgrAccountTransaction, LdgrAccountTransactionPage, Ldgr402Requirement, Ldgr402Verification, LdgrAsset, LdgrClientOptions, LdgrNetwork, LdgrTransaction, PaymentExpectation, PaymentVerification, SignedSubmission } from "./types";
73
103
  export { createWebCryptoSigner, generateLocalWallet, loadLocalWallet } from "./signer";
74
104
  export type { LdgrSigner, LocalLdgrWallet } from "./signer";
package/dist/index.js CHANGED
@@ -78,7 +78,27 @@ function queryPath(path, query) {
78
78
  }
79
79
 
80
80
  // src/index.ts
81
- import { resolveLdgrNetwork as resolveLdgrNetwork2, resolvePublicAssetCode } from "@ldgr/protocol";
81
+ import {
82
+ decodeLdgr402Payment as decodeLdgr402Payment3,
83
+ parseLdgr402Requirement as parseLdgr402Requirement3,
84
+ resolveLdgrNetwork as resolveLdgrNetwork2,
85
+ resolvePublicAssetCode
86
+ } from "@ldgr/protocol";
87
+ // src/ldgr402.ts
88
+ import {
89
+ decodeLdgr402Payment,
90
+ parseLdgr402Requirement
91
+ } from "@ldgr/protocol";
92
+ import { decodeLdgr402Payment as decodeLdgr402Payment2, encodeLdgr402Payment, parseLdgr402Payment, parseLdgr402Requirement as parseLdgr402Requirement2 } from "@ldgr/protocol";
93
+ function parseMerchantLdgr402Requirement(input, options = {}) {
94
+ return parseLdgr402Requirement(input, {
95
+ nowMs: options.nowMs,
96
+ allowExpired: true
97
+ });
98
+ }
99
+ function decodeMerchantLdgr402Payment(headerValue) {
100
+ return decodeLdgr402Payment(headerValue);
101
+ }
82
102
  // src/signer.ts
83
103
  import {
84
104
  deriveAddress,
@@ -165,6 +185,45 @@ function createLdgrClient(options) {
165
185
  }
166
186
  return asset;
167
187
  };
188
+ const verifyTransfer = async (transactionHash, expected) => {
189
+ const hash = normalizedHash(transactionHash);
190
+ const expectedAsset = resolveAsset((expected.asset ?? "dng").trim() || "dng");
191
+ let transaction;
192
+ try {
193
+ transaction = await transport.get(`/api/v1/payments/v0.1/transactions/${hash}`);
194
+ } catch (error) {
195
+ if (error instanceof LdgrClientError && error.status === 404)
196
+ return { status: "not_found", transactionHash: hash };
197
+ throw error;
198
+ }
199
+ const fields = transactionFields(transaction);
200
+ 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) {
201
+ return { status: "mismatch", transactionHash: hash };
202
+ }
203
+ return { status: "confirmed", transactionHash: hash, ...fields };
204
+ };
205
+ const verifyLdgr402 = async (proofHeader, requirementInput, options2 = {}) => {
206
+ const proof = decodeLdgr402Payment3(proofHeader);
207
+ const requirement = parseLdgr402Requirement3(requirementInput, {
208
+ nowMs: options2.nowMs,
209
+ allowExpired: true
210
+ });
211
+ const nowMs = options2.nowMs ?? Date.now();
212
+ if (ledgerId === undefined)
213
+ throw new TypeError("network is required for LDGR402 verification");
214
+ if (proof.ledgerId !== requirement.ledgerId || ledgerId !== requirement.ledgerId || proof.challenge !== requirement.challenge) {
215
+ return { status: "mismatch", transactionHash: proof.transactionHash };
216
+ }
217
+ if (requirement.expiresAt <= nowMs) {
218
+ return { status: "expired", transactionHash: proof.transactionHash, challenge: requirement.challenge };
219
+ }
220
+ return verifyTransfer(proof.transactionHash, {
221
+ to: requirement.recipient,
222
+ asset: requirement.asset,
223
+ amount: requirement.amount,
224
+ reference: requirement.challenge
225
+ });
226
+ };
168
227
  return {
169
228
  ledger: {
170
229
  get: () => transport.get("/api/v1/ledger"),
@@ -173,13 +232,31 @@ function createLdgrClient(options) {
173
232
  accounts: {
174
233
  listBalances: (address) => transport.get(`/api/v1/balances/${encodePathSegment(address)}`),
175
234
  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))}`)
235
+ getNextNonce: (address, asset) => transport.get(`/api/v1/nonces/${encodePathSegment(address)}/${encodePathSegment(resolveAsset(asset))}`),
236
+ getWalletPublicKey: (address) => transport.get(`/api/v1/wallets/${encodePathSegment(address)}/public-key`),
237
+ registerWalletPublicKey: (address, registration) => transport.post(`/api/v1/wallets/${encodePathSegment(address)}/public-key`, registration),
238
+ async listTransactions(address, options2 = {}) {
239
+ const path = queryPath(`/api/v1/explorer/addresses/${encodePathSegment(address)}/transactions`, {
240
+ before: options2.before,
241
+ asset: options2.asset === undefined ? undefined : resolveAsset(options2.asset)
242
+ });
243
+ try {
244
+ return await transport.get(path);
245
+ } catch (error) {
246
+ if (error instanceof LdgrClientError && error.status === 404)
247
+ return { transactions: [] };
248
+ throw error;
249
+ }
250
+ }
251
+ },
252
+ assets: {
253
+ get: (asset) => transport.get(`/api/v1/assets/${encodePathSegment(resolveAsset(asset))}`)
177
254
  },
178
255
  transactions: {
179
256
  get: (transactionHash) => transport.get(`/api/v1/transactions/${normalizedHash(transactionHash)}`)
180
257
  },
181
258
  amm: {
182
- listPools: () => transport.get("/api/v1/amm/pools"),
259
+ listPools: (cursor, limit) => transport.get(queryPath("/api/v1/amm/pools", { cursor, limit })),
183
260
  getPool: (poolId) => transport.get(`/api/v1/amm/pools/${encodePathSegment(poolId)}`),
184
261
  getPoolByPair: (assetA, assetB) => transport.get(`/api/v1/amm/pools/by-pair/${encodePathSegment(resolveAsset(assetA))}/${encodePathSegment(resolveAsset(assetB))}`),
185
262
  quoteSwap: (poolId, assetIn, amountIn) => transport.get(queryPath(`/api/v1/amm/pools/${encodePathSegment(poolId)}/quote`, { assetIn: resolveAsset(assetIn), amountIn })),
@@ -215,32 +292,16 @@ function createLdgrClient(options) {
215
292
  getPrimary: (address) => transport.get(`/api/v1/usernames/by-address/${encodePathSegment(address)}`),
216
293
  listOwned: (address, options2) => transport.get(queryPath(`/api/v1/usernames/owned-by/${encodePathSegment(address)}`, options2 ?? {}))
217
294
  },
218
- payments: {
219
- async verifyTransfer(transactionHash, expected) {
220
- const hash = normalizedHash(transactionHash);
221
- const expectedAsset = resolveAsset((expected.asset ?? "dng").trim() || "dng");
222
- let transaction;
223
- try {
224
- transaction = await transport.get(`/api/v1/payments/v0.1/transactions/${hash}`);
225
- } catch (error) {
226
- if (error instanceof LdgrClientError && error.status === 404)
227
- return { status: "not_found", transactionHash: hash };
228
- throw error;
229
- }
230
- const fields = transactionFields(transaction);
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) {
232
- return { status: "mismatch", transactionHash: hash };
233
- }
234
- return { status: "confirmed", transactionHash: hash, ...fields };
235
- }
236
- },
237
- submitSigned: (path, signed) => transport.post(path, signed)
295
+ payments: { verifyTransfer, verifyLdgr402 },
296
+ submitSigned: (path, signed) => transport.post(path, { ...signed, submittedAtMs: Date.now() })
238
297
  };
239
298
  }
240
299
  export {
241
- loadLocalWallet,
242
- generateLocalWallet,
243
- createWebCryptoSigner,
300
+ LdgrClientError,
244
301
  createLdgrClient,
245
- LdgrClientError
302
+ createWebCryptoSigner,
303
+ decodeMerchantLdgr402Payment,
304
+ generateLocalWallet,
305
+ loadLocalWallet,
306
+ parseMerchantLdgr402Requirement
246
307
  };
@@ -0,0 +1,10 @@
1
+ import { type Ldgr402Requirement } from "@ldgr/protocol";
2
+ export type { Ldgr402Requirement } from "@ldgr/protocol";
3
+ export { decodeLdgr402Payment, encodeLdgr402Payment, parseLdgr402Payment, parseLdgr402Requirement } from "@ldgr/protocol";
4
+ export type { Ldgr402ParseOptions, Ldgr402Payment } from "@ldgr/protocol";
5
+ export type Ldgr402VerificationOptions = {
6
+ readonly nowMs?: number;
7
+ };
8
+ export declare function parseMerchantLdgr402Requirement(input: unknown, options?: Ldgr402VerificationOptions): Ldgr402Requirement;
9
+ export declare function decodeMerchantLdgr402Payment(headerValue: string): import("@ldgr/protocol").Ldgr402Payment;
10
+ export type { Ldgr402Verification } from "./types";
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { LdgrNetwork } from "@ldgr/protocol";
1
+ import type { AssetBridgePolicy, LdgrNetwork } from "@ldgr/protocol";
2
2
  export type LedgerId = 0 | 1;
3
3
  export type { LdgrNetwork } from "@ldgr/protocol";
4
4
  export type LdgrTransaction = {
@@ -12,6 +12,38 @@ export type LdgrTransaction = {
12
12
  readonly transactionHash: string;
13
13
  readonly [key: string]: unknown;
14
14
  };
15
+ export type LdgrAccountTransaction = {
16
+ readonly sequence: string;
17
+ readonly action: string;
18
+ readonly asset: string;
19
+ readonly from: string;
20
+ readonly to?: string;
21
+ readonly amount?: string;
22
+ readonly fee?: string;
23
+ readonly transactionHash: string;
24
+ readonly serverTimestamp: string;
25
+ readonly metadata?: unknown;
26
+ readonly encryptedMessage?: string;
27
+ };
28
+ export type LdgrAccountTransactionPage = {
29
+ readonly transactions: readonly LdgrAccountTransaction[];
30
+ readonly nextBefore?: string;
31
+ };
32
+ /** Public asset metadata used to format user-facing balances and transfer amounts. */
33
+ export type LdgrAsset = {
34
+ readonly code: string;
35
+ readonly displayName: string;
36
+ readonly ticker?: string;
37
+ readonly decimals: number;
38
+ readonly active: boolean;
39
+ readonly bridgePolicy: AssetBridgePolicy;
40
+ /** Optional operator-registered token logo. */
41
+ readonly iconUri?: string;
42
+ /** Ops-set display-only verification state. */
43
+ readonly verified?: boolean;
44
+ readonly verificationKind?: "official" | "partner";
45
+ readonly [key: string]: unknown;
46
+ };
15
47
  export type PaymentExpectation = {
16
48
  readonly to: string;
17
49
  readonly asset?: string;
@@ -29,10 +61,17 @@ export type PaymentVerification = {
29
61
  readonly status: "not_found" | "mismatch";
30
62
  readonly transactionHash: string;
31
63
  };
64
+ export type Ldgr402Verification = PaymentVerification | {
65
+ readonly status: "expired";
66
+ readonly transactionHash: string;
67
+ readonly challenge: string;
68
+ };
69
+ export type { Ldgr402Requirement } from "@ldgr/protocol";
32
70
  export type SignedSubmission = {
33
71
  readonly payload: Record<string, unknown>;
34
72
  readonly signature: string;
35
73
  readonly feeSponsor?: unknown;
74
+ readonly submittedAtMs?: number;
36
75
  };
37
76
  export type LdgrClientOptions = {
38
77
  /** `testnet` / `0` or `mainnet` / `1`; required to use public native ids `t0` / `t1`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ldgr/client",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "exports": {
@@ -10,19 +10,16 @@
10
10
  }
11
11
  },
12
12
  "files": ["dist", "README.md"],
13
- "publishConfig": {
14
- "access": "public"
15
- },
16
- "engines": {
17
- "node": ">=20.19.0"
18
- },
19
- "dependencies": {
20
- "@ldgr/protocol": "^0.2.0"
21
- },
22
- "scripts": {
23
- "build": "rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm --packages external && tsc -p tsconfig.build.json",
24
- "prepack": "bun run build",
25
- "test": "bun test",
26
- "typecheck": "tsc -p tsconfig.json"
27
- }
13
+ "publishConfig": { "access": "public" },
14
+ "engines": { "node": ">=20.19.0" },
15
+ "dependencies": { "@ldgr/protocol": "^0.3.0" },
16
+ "scripts": {
17
+ "build": "rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm --packages external && tsc -p tsconfig.build.json",
18
+ "prepack": "bun run build",
19
+ "test": "bun test",
20
+ "typecheck": "tsc -p tsconfig.json"
21
+ },
22
+ "devDependencies": {
23
+ "typescript": "^7.0.2"
24
+ }
28
25
  }