@ldgr/client 0.2.1 → 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 +21 -0
- package/dist/index.d.ts +29 -3
- package/dist/index.js +84 -26
- package/dist/ldgr402.d.ts +10 -0
- package/dist/types.d.ts +29 -0
- package/package.json +3 -3
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
|
|
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,6 +25,27 @@ 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>;
|
|
27
49
|
};
|
|
28
50
|
assets: {
|
|
29
51
|
get: (asset: string) => Promise<LdgrAsset>;
|
|
@@ -66,13 +88,17 @@ export declare function createLdgrClient(options: LdgrClientOptions): {
|
|
|
66
88
|
}) => Promise<Record<string, unknown>>;
|
|
67
89
|
};
|
|
68
90
|
payments: {
|
|
69
|
-
verifyTransfer(transactionHash: string, expected: PaymentExpectation)
|
|
91
|
+
verifyTransfer: (transactionHash: string, expected: PaymentExpectation) => Promise<PaymentVerification>;
|
|
92
|
+
verifyLdgr402: (proofHeader: string, requirementInput: Ldgr402Requirement, options?: {
|
|
93
|
+
readonly nowMs?: number;
|
|
94
|
+
}) => Promise<Ldgr402Verification>;
|
|
70
95
|
};
|
|
71
96
|
submitSigned: <T extends {
|
|
72
97
|
readonly transactionHash?: string;
|
|
73
98
|
}>(path: string, signed: SignedSubmission) => Promise<T>;
|
|
74
99
|
};
|
|
75
100
|
export { LdgrClientError } from "./types";
|
|
76
|
-
export
|
|
101
|
+
export { decodeMerchantLdgr402Payment, parseMerchantLdgr402Requirement } from "./ldgr402";
|
|
102
|
+
export type { LdgrAccountTransaction, LdgrAccountTransactionPage, Ldgr402Requirement, Ldgr402Verification, LdgrAsset, LdgrClientOptions, LdgrNetwork, LdgrTransaction, PaymentExpectation, PaymentVerification, SignedSubmission } from "./types";
|
|
77
103
|
export { createWebCryptoSigner, generateLocalWallet, loadLocalWallet } from "./signer";
|
|
78
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 {
|
|
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,7 +232,22 @@ 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
|
+
}
|
|
177
251
|
},
|
|
178
252
|
assets: {
|
|
179
253
|
get: (asset) => transport.get(`/api/v1/assets/${encodePathSegment(resolveAsset(asset))}`)
|
|
@@ -218,32 +292,16 @@ function createLdgrClient(options) {
|
|
|
218
292
|
getPrimary: (address) => transport.get(`/api/v1/usernames/by-address/${encodePathSegment(address)}`),
|
|
219
293
|
listOwned: (address, options2) => transport.get(queryPath(`/api/v1/usernames/owned-by/${encodePathSegment(address)}`, options2 ?? {}))
|
|
220
294
|
},
|
|
221
|
-
payments: {
|
|
222
|
-
|
|
223
|
-
const hash = normalizedHash(transactionHash);
|
|
224
|
-
const expectedAsset = resolveAsset((expected.asset ?? "dng").trim() || "dng");
|
|
225
|
-
let transaction;
|
|
226
|
-
try {
|
|
227
|
-
transaction = await transport.get(`/api/v1/payments/v0.1/transactions/${hash}`);
|
|
228
|
-
} catch (error) {
|
|
229
|
-
if (error instanceof LdgrClientError && error.status === 404)
|
|
230
|
-
return { status: "not_found", transactionHash: hash };
|
|
231
|
-
throw error;
|
|
232
|
-
}
|
|
233
|
-
const fields = transactionFields(transaction);
|
|
234
|
-
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) {
|
|
235
|
-
return { status: "mismatch", transactionHash: hash };
|
|
236
|
-
}
|
|
237
|
-
return { status: "confirmed", transactionHash: hash, ...fields };
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
submitSigned: (path, signed) => transport.post(path, signed)
|
|
295
|
+
payments: { verifyTransfer, verifyLdgr402 },
|
|
296
|
+
submitSigned: (path, signed) => transport.post(path, { ...signed, submittedAtMs: Date.now() })
|
|
241
297
|
};
|
|
242
298
|
}
|
|
243
299
|
export {
|
|
244
|
-
|
|
245
|
-
generateLocalWallet,
|
|
246
|
-
createWebCryptoSigner,
|
|
300
|
+
LdgrClientError,
|
|
247
301
|
createLdgrClient,
|
|
248
|
-
|
|
302
|
+
createWebCryptoSigner,
|
|
303
|
+
decodeMerchantLdgr402Payment,
|
|
304
|
+
generateLocalWallet,
|
|
305
|
+
loadLocalWallet,
|
|
306
|
+
parseMerchantLdgr402Requirement
|
|
249
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
|
@@ -12,6 +12,23 @@ 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
|
+
};
|
|
15
32
|
/** Public asset metadata used to format user-facing balances and transfer amounts. */
|
|
16
33
|
export type LdgrAsset = {
|
|
17
34
|
readonly code: string;
|
|
@@ -20,6 +37,11 @@ export type LdgrAsset = {
|
|
|
20
37
|
readonly decimals: number;
|
|
21
38
|
readonly active: boolean;
|
|
22
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";
|
|
23
45
|
readonly [key: string]: unknown;
|
|
24
46
|
};
|
|
25
47
|
export type PaymentExpectation = {
|
|
@@ -39,10 +61,17 @@ export type PaymentVerification = {
|
|
|
39
61
|
readonly status: "not_found" | "mismatch";
|
|
40
62
|
readonly transactionHash: string;
|
|
41
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";
|
|
42
70
|
export type SignedSubmission = {
|
|
43
71
|
readonly payload: Record<string, unknown>;
|
|
44
72
|
readonly signature: string;
|
|
45
73
|
readonly feeSponsor?: unknown;
|
|
74
|
+
readonly submittedAtMs?: number;
|
|
46
75
|
};
|
|
47
76
|
export type LdgrClientOptions = {
|
|
48
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.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"files": ["dist", "README.md"],
|
|
13
13
|
"publishConfig": { "access": "public" },
|
|
14
14
|
"engines": { "node": ">=20.19.0" },
|
|
15
|
-
"dependencies": { "@ldgr/protocol": "^0.
|
|
15
|
+
"dependencies": { "@ldgr/protocol": "^0.3.0" },
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm --packages external && tsc -p tsconfig.build.json",
|
|
18
18
|
"prepack": "bun run build",
|
|
@@ -20,6 +20,6 @@
|
|
|
20
20
|
"typecheck": "tsc -p tsconfig.json"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"typescript": "^
|
|
23
|
+
"typescript": "^7.0.2"
|
|
24
24
|
}
|
|
25
25
|
}
|