@ldgr/client 0.4.0 → 0.6.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 +104 -0
- package/dist/index.d.ts +15 -23
- package/dist/index.js +260 -62
- package/dist/signer.d.ts +11 -1
- package/dist/types.d.ts +81 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -67,6 +67,57 @@ Settlement failures return `status: "failed"`; they do not have a transaction
|
|
|
67
67
|
hash or fee because the ledger rejected them before appending a transaction.
|
|
68
68
|
Transport and unexpected response errors still throw.
|
|
69
69
|
|
|
70
|
+
## Confidential payments
|
|
71
|
+
|
|
72
|
+
The product term is **confidential payments**. The compatibility-stable
|
|
73
|
+
`silentPayments.send()` API accepts a reusable LDGR payment code and a
|
|
74
|
+
`LocalLdgrWallet`. It signs and publishes the wallet's immutable sender-DH
|
|
75
|
+
public key, fetches the asset nonce, derives a fresh one-time destination, and
|
|
76
|
+
submits an ordinary transfer. A `NONCE_MISMATCH` causes one fresh derivation;
|
|
77
|
+
an ambiguous transport failure retries the exact signed request instead.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import {
|
|
81
|
+
createLdgrClient,
|
|
82
|
+
deriveLocalSilentPaymentIdentity,
|
|
83
|
+
loadLocalWallet,
|
|
84
|
+
} from "@ldgr/client";
|
|
85
|
+
|
|
86
|
+
const client = createLdgrClient({ network: "testnet" });
|
|
87
|
+
const senderWallet = await loadLocalWallet({
|
|
88
|
+
ledgerId: 0,
|
|
89
|
+
recoveryKey: await secretStore.read("ldgr-testnet-sender"),
|
|
90
|
+
});
|
|
91
|
+
// The receiver shares this code out of band; their recovery key never leaves
|
|
92
|
+
// their own wallet.
|
|
93
|
+
const receiver = deriveLocalSilentPaymentIdentity({
|
|
94
|
+
ledgerId: 0,
|
|
95
|
+
recoveryKey: await receiverSecretStore.read("ldgr-testnet-receiver"),
|
|
96
|
+
});
|
|
97
|
+
const result = await client.silentPayments.send({
|
|
98
|
+
wallet: senderWallet,
|
|
99
|
+
paymentCode: receiver.paymentCode,
|
|
100
|
+
asset: "t0",
|
|
101
|
+
amount: "2.50",
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If two byte-identical transport attempts both remain ambiguous,
|
|
106
|
+
`SilentPaymentSubmissionUnknownError` exposes the exact `submission`. Pass it
|
|
107
|
+
to `client.silentPayments.retry(error.submission)`; do not start a fresh send,
|
|
108
|
+
which would use the next nonce and could duplicate a payment that already
|
|
109
|
+
settled.
|
|
110
|
+
|
|
111
|
+
`silentPayments.scan(after, limit)` returns a bounded ascending page, its raw
|
|
112
|
+
ledger-sequence `nextAfter` checkpoint, and `moreAvailable`. Matching requires
|
|
113
|
+
the receiver scan secret and belongs in wallet code such as
|
|
114
|
+
`@ldgr/wallet-core`, never on a server that does not own the recovery key. This
|
|
115
|
+
LDGR-specific construction is not BIP-352-compatible and remains experimental.
|
|
116
|
+
Wallet discovery is available on both ledgers. The extension and Web Wallet
|
|
117
|
+
display reusable codes; the Web Wallet also sends to them. Spending or
|
|
118
|
+
consolidating confidential-payment outputs remains gated pending testnet-first
|
|
119
|
+
operational testing and external cryptographic review.
|
|
120
|
+
|
|
70
121
|
```ts
|
|
71
122
|
import { createLdgrClient } from "@ldgr/client";
|
|
72
123
|
|
|
@@ -90,6 +141,59 @@ transaction hash as the idempotency key. AMM quotes are estimates: pass an
|
|
|
90
141
|
explicit minimum output in the signed swap payload and re-quote immediately
|
|
91
142
|
before signing.
|
|
92
143
|
|
|
144
|
+
## Widget and Checkout merchant verification
|
|
145
|
+
|
|
146
|
+
Available in `@ldgr/client` **0.6.0**. These methods work on servers without
|
|
147
|
+
DOM globals or wallet keys. Configure the client's network from your saved
|
|
148
|
+
order and pass only the widget's identifiers as the submitted proof:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { createLdgrClient } from "@ldgr/client";
|
|
152
|
+
|
|
153
|
+
const order = await orders.load(orderId); // Your persisted server order.
|
|
154
|
+
const client = createLdgrClient({ network: order.network });
|
|
155
|
+
const payment = await client.payments.verifyPayment({
|
|
156
|
+
transactionHash: submitted.transactionHash,
|
|
157
|
+
checkoutSessionId: submitted.checkoutSessionId, // Optional for wallet payments.
|
|
158
|
+
}, {
|
|
159
|
+
to: order.recipient,
|
|
160
|
+
asset: order.assetCode,
|
|
161
|
+
amount: order.grossAmount, // Exact decimal string, including Checkout's fee.
|
|
162
|
+
reference: order.publicReference,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (payment.status === "confirmed") {
|
|
166
|
+
// In one database transaction: check order expiry, claim a unique
|
|
167
|
+
// (network, transactionHash), bind it to this order, and fulfil once.
|
|
168
|
+
await orders.fulfilOnce(order.id, order.network, payment.transactionHash);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`verifyPayment` explicitly dispatches to Checkout when `checkoutSessionId` is
|
|
173
|
+
provided; otherwise it requires a plain transfer. It never infers Checkout
|
|
174
|
+
from arbitrary batch metadata or falls back after a session mismatch. Expected
|
|
175
|
+
`to`, `asset`, gross decimal `amount`, and `reference` must come from your server,
|
|
176
|
+
not from the browser. Amount comparisons use exact strings, never floating point.
|
|
177
|
+
|
|
178
|
+
For a known Checkout session, use `payments.verifyCheckout(sessionId, {
|
|
179
|
+
to, asset, amount, reference, transactionHash? })`. It checks the authoritative
|
|
180
|
+
session ID, ledger, recipient, asset, gross amount, reference, paid state, and
|
|
181
|
+
optional submitted hash. A confirmed result includes `sessionId`,
|
|
182
|
+
`transactionHash`, `from`, `to`, `asset`, gross `amount`, `merchantAmount`, and
|
|
183
|
+
`checkoutFee`. With the default fee, a gross `1` USDL receipt reports merchant
|
|
184
|
+
`0.995` USDL and Checkout fee `0.005` USDL. Do not compare the net receipt with
|
|
185
|
+
the gross order amount.
|
|
186
|
+
|
|
187
|
+
Non-confirmed Checkout results are `pending`, `expired`, `not_found`, or
|
|
188
|
+
`mismatch`. An expired funding instruction does not invalidate a paid receipt.
|
|
189
|
+
Network/server errors and invalid arguments throw; callers must not fulfil on
|
|
190
|
+
an error. `verifyTransfer` remains a strict plain-transfer verifier. It also
|
|
191
|
+
checks that the response hash matches the requested hash.
|
|
192
|
+
|
|
193
|
+
The SDK reads receipts; your service owns authentication, order persistence,
|
|
194
|
+
order expiry rules, and atomic duplicate-payment protection. Browser SDK
|
|
195
|
+
`getPaymentStatus` / `getCheckoutStatus` results are UX hints only.
|
|
196
|
+
|
|
93
197
|
## LDGR402 merchant verification
|
|
94
198
|
|
|
95
199
|
For a persisted LDGR402 requirement and the browser's
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type LdgrTransferResult, type SendTransferInput } from "./transfer";
|
|
2
|
-
import { type Ldgr402Requirement
|
|
3
|
-
import { type LdgrAccountTransactionPage, type LdgrClientOptions, type LdgrAsset, type Ldgr402Verification, type LdgrTransaction, type PaymentExpectation, type PaymentVerification, type SignedSubmission } from "./types";
|
|
2
|
+
import { type Ldgr402Requirement } from "@ldgr/protocol";
|
|
3
|
+
import { type LdgrAccountTransactionPage, type LdgrClientOptions, type LdgrAsset, type Ldgr402Verification, type LdgrTransaction, type PaymentExpectation, type PaymentVerification, type CheckoutPaymentExpectation, type CheckoutPaymentVerification, type MerchantPaymentExpectation, type MerchantPaymentVerification, type PaymentSubmission, type SignedSubmission, type SilentPaymentSendInput, type SilentPaymentScanPage, type WalletPublicKeyRecord, type WalletPublicKeyRegistration } from "./types";
|
|
4
4
|
export declare function createLdgrClient(options: LdgrClientOptions): {
|
|
5
5
|
ledger: {
|
|
6
6
|
get: () => Promise<{
|
|
@@ -26,23 +26,8 @@ export declare function createLdgrClient(options: LdgrClientOptions): {
|
|
|
26
26
|
readonly asset: string;
|
|
27
27
|
readonly nextNonce: string;
|
|
28
28
|
}>;
|
|
29
|
-
getWalletPublicKey: (address: string) => Promise<
|
|
30
|
-
|
|
31
|
-
readonly publicKey: string;
|
|
32
|
-
}>;
|
|
33
|
-
registerWalletPublicKey: (address: string, registration: {
|
|
34
|
-
readonly version: 1;
|
|
35
|
-
readonly action: "wallet_key_register";
|
|
36
|
-
readonly chain: "ldgr-v1";
|
|
37
|
-
readonly ledgerId: LedgerId;
|
|
38
|
-
readonly from: string;
|
|
39
|
-
readonly publicKey: string;
|
|
40
|
-
readonly timestamp: string;
|
|
41
|
-
readonly signature: string;
|
|
42
|
-
}) => Promise<{
|
|
43
|
-
readonly address: string;
|
|
44
|
-
readonly publicKey: string;
|
|
45
|
-
}>;
|
|
29
|
+
getWalletPublicKey: (address: string) => Promise<WalletPublicKeyRecord>;
|
|
30
|
+
registerWalletPublicKey: (address: string, registration: WalletPublicKeyRegistration) => Promise<WalletPublicKeyRecord>;
|
|
46
31
|
listTransactions(address: string, options?: {
|
|
47
32
|
readonly before?: string;
|
|
48
33
|
readonly asset?: string;
|
|
@@ -90,10 +75,17 @@ export declare function createLdgrClient(options: LdgrClientOptions): {
|
|
|
90
75
|
};
|
|
91
76
|
payments: {
|
|
92
77
|
verifyTransfer: (transactionHash: string, expected: PaymentExpectation) => Promise<PaymentVerification>;
|
|
78
|
+
verifyCheckout: (sessionId: string, expected: CheckoutPaymentExpectation) => Promise<CheckoutPaymentVerification>;
|
|
79
|
+
verifyPayment: (submission: PaymentSubmission, expected: MerchantPaymentExpectation) => Promise<MerchantPaymentVerification>;
|
|
93
80
|
verifyLdgr402: (proofHeader: string, requirementInput: Ldgr402Requirement, options?: {
|
|
94
81
|
readonly nowMs?: number;
|
|
95
82
|
}) => Promise<Ldgr402Verification>;
|
|
96
83
|
};
|
|
84
|
+
silentPayments: {
|
|
85
|
+
scan: (after?: string, limit?: number) => Promise<SilentPaymentScanPage>;
|
|
86
|
+
send: (input: SilentPaymentSendInput) => Promise<LdgrTransferResult>;
|
|
87
|
+
retry(submission: SignedSubmission): Promise<LdgrTransferResult>;
|
|
88
|
+
};
|
|
97
89
|
transfers: {
|
|
98
90
|
send: (input: SendTransferInput) => Promise<LdgrTransferResult>;
|
|
99
91
|
};
|
|
@@ -101,10 +93,10 @@ export declare function createLdgrClient(options: LdgrClientOptions): {
|
|
|
101
93
|
readonly transactionHash?: string;
|
|
102
94
|
}>(path: string, signed: SignedSubmission) => Promise<T>;
|
|
103
95
|
};
|
|
104
|
-
export { LdgrClientError } from "./types";
|
|
96
|
+
export { LdgrClientError, SilentPaymentSubmissionUnknownError } from "./types";
|
|
105
97
|
export { decodeMerchantLdgr402Payment, parseMerchantLdgr402Requirement } from "./ldgr402";
|
|
106
|
-
export type { LdgrAccountTransaction, LdgrAccountTransactionPage, Ldgr402Requirement, Ldgr402Verification, LdgrAsset, LdgrClientOptions, LdgrNetwork, LdgrTransaction, PaymentExpectation, PaymentVerification, SignedSubmission } from "./types";
|
|
107
|
-
export { createWebCryptoSigner, generateLocalWallet, loadLocalWallet } from "./signer";
|
|
108
|
-
export type { LdgrSigner, LocalLdgrWallet } from "./signer";
|
|
98
|
+
export type { CheckoutPaymentExpectation, CheckoutPaymentVerification, MerchantPaymentExpectation, MerchantPaymentVerification, PaymentSubmission, LdgrAccountTransaction, LdgrAccountTransactionPage, Ldgr402Requirement, Ldgr402Verification, LdgrAsset, LdgrClientOptions, LdgrNetwork, LdgrTransaction, PaymentExpectation, PaymentVerification, SignedSubmission, SilentPaymentScanPage, SilentPaymentScanRecord, SilentPaymentSendInput, WalletPublicKeyRecord, WalletPublicKeyRegistration } from "./types";
|
|
99
|
+
export { createWebCryptoSigner, deriveLocalSilentPaymentIdentity, generateLocalWallet, loadLocalWallet } from "./signer";
|
|
100
|
+
export type { LdgrSigner, LocalLdgrWallet, LocalSilentPaymentIdentity } from "./signer";
|
|
109
101
|
export { buildTransferSubmission } from "./transfer";
|
|
110
102
|
export type { BuildTransferSubmissionInput, LdgrTransferResult, SendTransferInput } from "./transfer";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
import { resolveLdgrNetwork } from "@ldgr/protocol";
|
|
3
3
|
|
|
4
4
|
// src/types.ts
|
|
5
|
+
class SilentPaymentSubmissionUnknownError extends Error {
|
|
6
|
+
submission;
|
|
7
|
+
constructor(submission, cause) {
|
|
8
|
+
super("Could not confirm silent payment submission; retry this exact signed submission", { cause });
|
|
9
|
+
this.name = "SilentPaymentSubmissionUnknownError";
|
|
10
|
+
this.submission = submission;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
class LdgrClientError extends Error {
|
|
6
15
|
status;
|
|
7
16
|
code;
|
|
@@ -77,6 +86,67 @@ function queryPath(path, query) {
|
|
|
77
86
|
return encoded ? `${path}?${encoded}` : path;
|
|
78
87
|
}
|
|
79
88
|
|
|
89
|
+
// src/signer.ts
|
|
90
|
+
import {
|
|
91
|
+
deriveAddress,
|
|
92
|
+
deriveSilentPaymentIdentity,
|
|
93
|
+
decodeEd25519RecoveryKey,
|
|
94
|
+
encodeEd25519RecoveryKey,
|
|
95
|
+
exportEd25519PublicKey,
|
|
96
|
+
generateEd25519KeyPair,
|
|
97
|
+
importEd25519PrivateSeed,
|
|
98
|
+
signCanonicalPayload
|
|
99
|
+
} from "@ldgr/protocol";
|
|
100
|
+
function encodeBase64Url(bytes) {
|
|
101
|
+
let binary = "";
|
|
102
|
+
for (const byte of bytes)
|
|
103
|
+
binary += String.fromCharCode(byte);
|
|
104
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
105
|
+
}
|
|
106
|
+
function deriveLocalSilentPaymentIdentity(input) {
|
|
107
|
+
const { privateSeed } = decodeEd25519RecoveryKey(input.recoveryKey);
|
|
108
|
+
const identity = deriveSilentPaymentIdentity(privateSeed, input.ledgerId);
|
|
109
|
+
return {
|
|
110
|
+
paymentCode: identity.paymentCode,
|
|
111
|
+
senderDhPublicKey: encodeBase64Url(identity.senderDhPublicKey),
|
|
112
|
+
receiverScanPublicKey: encodeBase64Url(identity.receiverScanPublicKey),
|
|
113
|
+
spendPublicKey: encodeBase64Url(identity.spendPublicKey)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function createWebCryptoSigner(input) {
|
|
117
|
+
const publicKey = encodeBase64Url(await exportEd25519PublicKey(input.publicKey));
|
|
118
|
+
const address = await deriveAddress(input.publicKey, input.ledgerId);
|
|
119
|
+
return {
|
|
120
|
+
address,
|
|
121
|
+
publicKey,
|
|
122
|
+
ledgerId: input.ledgerId,
|
|
123
|
+
async sign(payload) {
|
|
124
|
+
if (payload.ledgerId !== input.ledgerId)
|
|
125
|
+
throw new TypeError("payload ledgerId does not match signer ledgerId");
|
|
126
|
+
if (payload.from !== address)
|
|
127
|
+
throw new TypeError("payload from does not match signer address");
|
|
128
|
+
if (payload.publicKey !== undefined && payload.publicKey !== publicKey) {
|
|
129
|
+
throw new TypeError("payload publicKey does not match signer publicKey");
|
|
130
|
+
}
|
|
131
|
+
return encodeBase64Url(await signCanonicalPayload(input.privateKey, { ...payload, publicKey }));
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function generateLocalWallet(input) {
|
|
136
|
+
const keyPair = await generateEd25519KeyPair();
|
|
137
|
+
const signer = await createWebCryptoSigner({ ...input, ...keyPair });
|
|
138
|
+
const privateKeyJwk = await globalThis.crypto.subtle.exportKey("jwk", keyPair.privateKey);
|
|
139
|
+
return {
|
|
140
|
+
...signer,
|
|
141
|
+
recoveryKey: encodeEd25519RecoveryKey(privateKeyJwk)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async function loadLocalWallet(input) {
|
|
145
|
+
const { privateSeed } = decodeEd25519RecoveryKey(input.recoveryKey);
|
|
146
|
+
const keyPair = await importEd25519PrivateSeed(privateSeed);
|
|
147
|
+
return { ...await createWebCryptoSigner({ ...input, ...keyPair }), recoveryKey: input.recoveryKey };
|
|
148
|
+
}
|
|
149
|
+
|
|
80
150
|
// src/transfer.ts
|
|
81
151
|
import {
|
|
82
152
|
canonicalizeLedgerAddress,
|
|
@@ -127,6 +197,10 @@ async function buildTransferSubmission(input) {
|
|
|
127
197
|
// src/index.ts
|
|
128
198
|
import {
|
|
129
199
|
decodeLdgr402Payment as decodeLdgr402Payment3,
|
|
200
|
+
decodeEd25519RecoveryKey as decodeEd25519RecoveryKey2,
|
|
201
|
+
decodeSilentPaymentCode,
|
|
202
|
+
deriveSilentPaymentIdentity as deriveSilentPaymentIdentity2,
|
|
203
|
+
deriveSilentPaymentSenderOutput,
|
|
130
204
|
nativeAssetDefinitionForLedger,
|
|
131
205
|
parseLdgr402Requirement as parseLdgr402Requirement3,
|
|
132
206
|
resolveLdgrNetwork as resolveLdgrNetwork2,
|
|
@@ -147,60 +221,11 @@ function parseMerchantLdgr402Requirement(input, options = {}) {
|
|
|
147
221
|
function decodeMerchantLdgr402Payment(headerValue) {
|
|
148
222
|
return decodeLdgr402Payment(headerValue);
|
|
149
223
|
}
|
|
150
|
-
// src/signer.ts
|
|
151
|
-
import {
|
|
152
|
-
deriveAddress,
|
|
153
|
-
decodeEd25519RecoveryKey,
|
|
154
|
-
encodeEd25519RecoveryKey,
|
|
155
|
-
exportEd25519PublicKey,
|
|
156
|
-
generateEd25519KeyPair,
|
|
157
|
-
importEd25519PrivateSeed,
|
|
158
|
-
signCanonicalPayload
|
|
159
|
-
} from "@ldgr/protocol";
|
|
160
|
-
function encodeBase64Url(bytes) {
|
|
161
|
-
let binary = "";
|
|
162
|
-
for (const byte of bytes)
|
|
163
|
-
binary += String.fromCharCode(byte);
|
|
164
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
165
|
-
}
|
|
166
|
-
async function createWebCryptoSigner(input) {
|
|
167
|
-
const publicKey = encodeBase64Url(await exportEd25519PublicKey(input.publicKey));
|
|
168
|
-
const address = await deriveAddress(input.publicKey, input.ledgerId);
|
|
169
|
-
return {
|
|
170
|
-
address,
|
|
171
|
-
publicKey,
|
|
172
|
-
ledgerId: input.ledgerId,
|
|
173
|
-
async sign(payload) {
|
|
174
|
-
if (payload.ledgerId !== input.ledgerId)
|
|
175
|
-
throw new TypeError("payload ledgerId does not match signer ledgerId");
|
|
176
|
-
if (payload.from !== address)
|
|
177
|
-
throw new TypeError("payload from does not match signer address");
|
|
178
|
-
if (payload.publicKey !== undefined && payload.publicKey !== publicKey) {
|
|
179
|
-
throw new TypeError("payload publicKey does not match signer publicKey");
|
|
180
|
-
}
|
|
181
|
-
return encodeBase64Url(await signCanonicalPayload(input.privateKey, { ...payload, publicKey }));
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
async function generateLocalWallet(input) {
|
|
186
|
-
const keyPair = await generateEd25519KeyPair();
|
|
187
|
-
const signer = await createWebCryptoSigner({ ...input, ...keyPair });
|
|
188
|
-
const privateKeyJwk = await globalThis.crypto.subtle.exportKey("jwk", keyPair.privateKey);
|
|
189
|
-
return {
|
|
190
|
-
...signer,
|
|
191
|
-
recoveryKey: encodeEd25519RecoveryKey(privateKeyJwk)
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
async function loadLocalWallet(input) {
|
|
195
|
-
const { privateSeed } = decodeEd25519RecoveryKey(input.recoveryKey);
|
|
196
|
-
const keyPair = await importEd25519PrivateSeed(privateSeed);
|
|
197
|
-
return createWebCryptoSigner({ ...input, ...keyPair });
|
|
198
|
-
}
|
|
199
224
|
|
|
200
225
|
// src/index.ts
|
|
201
226
|
function normalizedHash(transactionHash) {
|
|
202
227
|
const hash = transactionHash.toLowerCase();
|
|
203
|
-
if (!/^[0-9a-f]{64}$/.test(hash))
|
|
228
|
+
if (hash.length !== 64 || !/^[0-9a-f]{64}$/.test(hash))
|
|
204
229
|
throw new TypeError("transactionHash must be a 64-character hexadecimal hash");
|
|
205
230
|
return hash;
|
|
206
231
|
}
|
|
@@ -222,6 +247,18 @@ function paymentReference(metadata) {
|
|
|
222
247
|
function transactionFields(transaction) {
|
|
223
248
|
return typeof transaction.from === "string" && typeof transaction.to === "string" && typeof transaction.asset === "string" && typeof transaction.amount === "string" ? { from: transaction.from, to: transaction.to, asset: transaction.asset, amount: transaction.amount } : null;
|
|
224
249
|
}
|
|
250
|
+
function assertMerchantTerms(expected) {
|
|
251
|
+
if (typeof expected.to !== "string" || !expected.to)
|
|
252
|
+
throw new TypeError("to is required");
|
|
253
|
+
if (typeof expected.asset !== "string" || !expected.asset.trim())
|
|
254
|
+
throw new TypeError("asset is required");
|
|
255
|
+
if (typeof expected.reference !== "string" || !expected.reference || new TextEncoder().encode(expected.reference).byteLength > 128) {
|
|
256
|
+
throw new TypeError("reference must be 1 to 128 UTF-8 bytes");
|
|
257
|
+
}
|
|
258
|
+
if (typeof expected.amount !== "string" || !sameDecimalAmount(expected.amount, expected.amount) || !/[1-9]/.test(expected.amount)) {
|
|
259
|
+
throw new TypeError("amount must be a positive exact decimal string");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
225
262
|
function isNonceMismatch(error) {
|
|
226
263
|
return error instanceof LdgrClientError && error.code === "NONCE_MISMATCH";
|
|
227
264
|
}
|
|
@@ -257,18 +294,65 @@ function createLdgrClient(options) {
|
|
|
257
294
|
throw error;
|
|
258
295
|
}
|
|
259
296
|
const fields = transactionFields(transaction);
|
|
260
|
-
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) {
|
|
297
|
+
if (transaction.transactionHash !== hash || 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) {
|
|
261
298
|
return { status: "mismatch", transactionHash: hash };
|
|
262
299
|
}
|
|
263
300
|
return { status: "confirmed", transactionHash: hash, ...fields };
|
|
264
301
|
};
|
|
265
|
-
const
|
|
302
|
+
const verifyCheckout = async (sessionId, expected) => {
|
|
303
|
+
if (ledgerId === undefined)
|
|
304
|
+
throw new TypeError("network is required for Checkout verification");
|
|
305
|
+
if (sessionId.length !== 36 || !/^chk_[0-9a-f]{32}$/.test(sessionId))
|
|
306
|
+
throw new TypeError("Invalid Checkout session ID");
|
|
307
|
+
assertMerchantTerms(expected);
|
|
308
|
+
const asset = resolveAsset(expected.asset.trim());
|
|
309
|
+
const expectedHash = expected.transactionHash === undefined ? undefined : normalizedHash(expected.transactionHash);
|
|
310
|
+
let body;
|
|
311
|
+
try {
|
|
312
|
+
body = await transport.get(`/api/v1/checkout/sessions/${sessionId}`);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (error instanceof LdgrClientError && error.status === 404)
|
|
315
|
+
return { status: "not_found", sessionId };
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
const mismatch = { status: "mismatch", sessionId };
|
|
319
|
+
if (body === null || typeof body !== "object" || Array.isArray(body))
|
|
320
|
+
return mismatch;
|
|
321
|
+
const session = body;
|
|
322
|
+
if (session.id !== sessionId || session.ledgerId !== ledgerId || session.recipient !== expected.to || session.asset !== asset || typeof session.amount !== "string" || !sameDecimalAmount(session.amount, expected.amount) || session.paymentReference !== expected.reference)
|
|
323
|
+
return mismatch;
|
|
324
|
+
if (session.status === "open")
|
|
325
|
+
return { status: "pending", sessionId };
|
|
326
|
+
if (session.status === "expired")
|
|
327
|
+
return { status: "expired", sessionId };
|
|
328
|
+
if (session.status !== "paid" || typeof session.transactionHash !== "string" || session.transactionHash.length !== 64 || !/^[0-9a-f]{64}$/.test(session.transactionHash) || expectedHash !== undefined && session.transactionHash !== expectedHash || typeof session.guestAddress !== "string" || !session.guestAddress || typeof session.merchantAmount !== "string" || !sameDecimalAmount(session.merchantAmount, session.merchantAmount) || typeof session.checkoutFee !== "string" || !sameDecimalAmount(session.checkoutFee, session.checkoutFee))
|
|
329
|
+
return mismatch;
|
|
330
|
+
return {
|
|
331
|
+
status: "confirmed",
|
|
332
|
+
sessionId,
|
|
333
|
+
transactionHash: session.transactionHash,
|
|
334
|
+
from: session.guestAddress,
|
|
335
|
+
to: expected.to,
|
|
336
|
+
asset,
|
|
337
|
+
amount: session.amount,
|
|
338
|
+
merchantAmount: session.merchantAmount,
|
|
339
|
+
checkoutFee: session.checkoutFee
|
|
340
|
+
};
|
|
341
|
+
};
|
|
342
|
+
const verifyPayment = async (submission, expected) => {
|
|
343
|
+
if (ledgerId === undefined)
|
|
344
|
+
throw new TypeError("network is required for merchant payment verification");
|
|
345
|
+
assertMerchantTerms(expected);
|
|
346
|
+
const hash = normalizedHash(submission.transactionHash);
|
|
347
|
+
return submission.checkoutSessionId === undefined ? verifyTransfer(hash, expected) : verifyCheckout(submission.checkoutSessionId, { ...expected, transactionHash: hash });
|
|
348
|
+
};
|
|
349
|
+
const verifyLdgr402 = async (proofHeader, requirementInput, options = {}) => {
|
|
266
350
|
const proof = decodeLdgr402Payment3(proofHeader);
|
|
267
351
|
const requirement = parseLdgr402Requirement3(requirementInput, {
|
|
268
|
-
nowMs:
|
|
352
|
+
nowMs: options.nowMs,
|
|
269
353
|
allowExpired: true
|
|
270
354
|
});
|
|
271
|
-
const nowMs =
|
|
355
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
272
356
|
if (ledgerId === undefined)
|
|
273
357
|
throw new TypeError("network is required for LDGR402 verification");
|
|
274
358
|
if (proof.ledgerId !== requirement.ledgerId || ledgerId !== requirement.ledgerId || proof.challenge !== requirement.challenge) {
|
|
@@ -323,9 +407,101 @@ function createLdgrClient(options) {
|
|
|
323
407
|
try {
|
|
324
408
|
return await submit();
|
|
325
409
|
} catch (retryError) {
|
|
326
|
-
const
|
|
327
|
-
if (
|
|
328
|
-
return
|
|
410
|
+
const failure = transferFailure(retryError);
|
|
411
|
+
if (failure !== undefined)
|
|
412
|
+
return failure;
|
|
413
|
+
throw retryError;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const failure = transferFailure(error);
|
|
417
|
+
if (failure !== undefined)
|
|
418
|
+
return failure;
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
const sendSilentPayment = async (input) => {
|
|
423
|
+
if (ledgerId !== undefined && ledgerId !== input.wallet.ledgerId) {
|
|
424
|
+
throw new TypeError("signer ledgerId does not match client network");
|
|
425
|
+
}
|
|
426
|
+
const requestedAsset = input.asset.trim();
|
|
427
|
+
if (!requestedAsset)
|
|
428
|
+
throw new TypeError("asset is required");
|
|
429
|
+
const asset = resolvePublicAssetCode(requestedAsset, input.wallet.ledgerId);
|
|
430
|
+
decodeSilentPaymentCode(input.paymentCode, input.wallet.ledgerId);
|
|
431
|
+
const assetDefinition = asset === "dng" ? nativeAssetDefinitionForLedger(input.wallet.ledgerId) : await transport.get(`/api/v1/assets/${encodePathSegment(asset)}`);
|
|
432
|
+
const senderIdentity = deriveSilentPaymentIdentity2(decodeEd25519RecoveryKey2(input.wallet.recoveryKey).privateSeed, input.wallet.ledgerId);
|
|
433
|
+
const registrationPayload = {
|
|
434
|
+
version: 1,
|
|
435
|
+
action: "wallet_key_register",
|
|
436
|
+
chain: "ldgr-v1",
|
|
437
|
+
ledgerId: input.wallet.ledgerId,
|
|
438
|
+
from: input.wallet.address,
|
|
439
|
+
publicKey: input.wallet.publicKey,
|
|
440
|
+
senderDhPublicKey: deriveLocalSilentPaymentIdentity(input.wallet).senderDhPublicKey,
|
|
441
|
+
timestamp: String((input.now ?? Date.now)())
|
|
442
|
+
};
|
|
443
|
+
await transport.post(`/api/v1/wallets/${encodePathSegment(input.wallet.address)}/public-key`, { ...registrationPayload, signature: await input.wallet.sign(registrationPayload) });
|
|
444
|
+
const submit = async () => {
|
|
445
|
+
const nonce = await transport.get(`/api/v1/nonces/${encodePathSegment(input.wallet.address)}/${encodePathSegment(asset)}`);
|
|
446
|
+
if (!/^(0|[1-9][0-9]*)$/.test(nonce.nextNonce))
|
|
447
|
+
throw new TypeError("Ledger returned an invalid next nonce");
|
|
448
|
+
const output = deriveSilentPaymentSenderOutput({
|
|
449
|
+
senderDhPrivateKey: senderIdentity.senderDhPrivateKey,
|
|
450
|
+
paymentCode: input.paymentCode,
|
|
451
|
+
chain: "ldgr-v1",
|
|
452
|
+
ledgerId: input.wallet.ledgerId,
|
|
453
|
+
senderAddress: input.wallet.address,
|
|
454
|
+
action: "transfer",
|
|
455
|
+
asset,
|
|
456
|
+
nonce: BigInt(nonce.nextNonce),
|
|
457
|
+
outputIndex: 0
|
|
458
|
+
});
|
|
459
|
+
const submission = await buildTransferSubmission({
|
|
460
|
+
wallet: input.wallet,
|
|
461
|
+
asset,
|
|
462
|
+
assetDefinition,
|
|
463
|
+
amount: input.amount,
|
|
464
|
+
to: output.address,
|
|
465
|
+
nextNonce: nonce.nextNonce,
|
|
466
|
+
...input.memo === undefined ? {} : { memo: input.memo },
|
|
467
|
+
...input.metadata === undefined ? {} : { metadata: input.metadata },
|
|
468
|
+
...input.now === undefined ? {} : { now: input.now }
|
|
469
|
+
});
|
|
470
|
+
const feeSponsor = input.buildFeeSponsor === undefined ? undefined : await input.buildFeeSponsor(submission);
|
|
471
|
+
const requestBody = {
|
|
472
|
+
...submission,
|
|
473
|
+
...feeSponsor === undefined ? {} : { feeSponsor },
|
|
474
|
+
submittedAtMs: Date.now()
|
|
475
|
+
};
|
|
476
|
+
let transaction;
|
|
477
|
+
try {
|
|
478
|
+
transaction = await transport.post("/api/v1/transfers", requestBody);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
if (error instanceof LdgrClientError)
|
|
481
|
+
throw error;
|
|
482
|
+
try {
|
|
483
|
+
transaction = await transport.post("/api/v1/transfers", requestBody);
|
|
484
|
+
} catch (retryError) {
|
|
485
|
+
if (retryError instanceof LdgrClientError)
|
|
486
|
+
throw retryError;
|
|
487
|
+
throw new SilentPaymentSubmissionUnknownError(requestBody, retryError);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (typeof transaction.transactionHash !== "string" || typeof transaction.fee !== "string" || typeof transaction.feeAsset !== "string") {
|
|
491
|
+
throw new TypeError("LDGR transfer response is missing transaction hash or fee");
|
|
492
|
+
}
|
|
493
|
+
return { status: "settled", transactionHash: transaction.transactionHash, fee: transaction.fee, feeAsset: transaction.feeAsset, transaction };
|
|
494
|
+
};
|
|
495
|
+
try {
|
|
496
|
+
return await submit();
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (isNonceMismatch(error)) {
|
|
499
|
+
try {
|
|
500
|
+
return await submit();
|
|
501
|
+
} catch (retryError) {
|
|
502
|
+
const failure = transferFailure(retryError);
|
|
503
|
+
if (failure !== undefined)
|
|
504
|
+
return failure;
|
|
329
505
|
throw retryError;
|
|
330
506
|
}
|
|
331
507
|
}
|
|
@@ -346,10 +522,10 @@ function createLdgrClient(options) {
|
|
|
346
522
|
getNextNonce: (address, asset) => transport.get(`/api/v1/nonces/${encodePathSegment(address)}/${encodePathSegment(resolveAsset(asset))}`),
|
|
347
523
|
getWalletPublicKey: (address) => transport.get(`/api/v1/wallets/${encodePathSegment(address)}/public-key`),
|
|
348
524
|
registerWalletPublicKey: (address, registration) => transport.post(`/api/v1/wallets/${encodePathSegment(address)}/public-key`, registration),
|
|
349
|
-
async listTransactions(address,
|
|
525
|
+
async listTransactions(address, options = {}) {
|
|
350
526
|
const path = queryPath(`/api/v1/explorer/addresses/${encodePathSegment(address)}/transactions`, {
|
|
351
|
-
before:
|
|
352
|
-
asset:
|
|
527
|
+
before: options.before,
|
|
528
|
+
asset: options.asset === undefined ? undefined : resolveAsset(options.asset)
|
|
353
529
|
});
|
|
354
530
|
try {
|
|
355
531
|
return await transport.get(path);
|
|
@@ -401,19 +577,41 @@ function createLdgrClient(options) {
|
|
|
401
577
|
usernames: {
|
|
402
578
|
get: (name) => transport.get(`/api/v1/usernames/${encodePathSegment(name)}`),
|
|
403
579
|
getPrimary: (address) => transport.get(`/api/v1/usernames/by-address/${encodePathSegment(address)}`),
|
|
404
|
-
listOwned: (address,
|
|
580
|
+
listOwned: (address, options) => transport.get(queryPath(`/api/v1/usernames/owned-by/${encodePathSegment(address)}`, options ?? {}))
|
|
581
|
+
},
|
|
582
|
+
payments: { verifyTransfer, verifyCheckout, verifyPayment, verifyLdgr402 },
|
|
583
|
+
silentPayments: {
|
|
584
|
+
scan: (after = "0", limit) => transport.get(queryPath("/api/v1/silent-payments/scan", { after, limit })),
|
|
585
|
+
send: sendSilentPayment,
|
|
586
|
+
async retry(submission) {
|
|
587
|
+
try {
|
|
588
|
+
const transaction = await transport.post("/api/v1/transfers", submission);
|
|
589
|
+
if (typeof transaction.transactionHash !== "string" || typeof transaction.fee !== "string" || typeof transaction.feeAsset !== "string") {
|
|
590
|
+
throw new TypeError("LDGR transfer response is missing transaction hash or fee");
|
|
591
|
+
}
|
|
592
|
+
return { status: "settled", transactionHash: transaction.transactionHash, fee: transaction.fee, feeAsset: transaction.feeAsset, transaction };
|
|
593
|
+
} catch (error) {
|
|
594
|
+
const failure = transferFailure(error);
|
|
595
|
+
if (failure !== undefined)
|
|
596
|
+
return failure;
|
|
597
|
+
if (error instanceof LdgrClientError)
|
|
598
|
+
throw error;
|
|
599
|
+
throw new SilentPaymentSubmissionUnknownError(submission, error);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
405
602
|
},
|
|
406
|
-
payments: { verifyTransfer, verifyLdgr402 },
|
|
407
603
|
transfers: { send: sendTransfer },
|
|
408
604
|
submitSigned: (path, signed) => transport.post(path, { ...signed, submittedAtMs: Date.now() })
|
|
409
605
|
};
|
|
410
606
|
}
|
|
411
607
|
export {
|
|
412
608
|
LdgrClientError,
|
|
609
|
+
SilentPaymentSubmissionUnknownError,
|
|
413
610
|
buildTransferSubmission,
|
|
414
611
|
createLdgrClient,
|
|
415
612
|
createWebCryptoSigner,
|
|
416
613
|
decodeMerchantLdgr402Payment,
|
|
614
|
+
deriveLocalSilentPaymentIdentity,
|
|
417
615
|
generateLocalWallet,
|
|
418
616
|
loadLocalWallet,
|
|
419
617
|
parseMerchantLdgr402Requirement
|
package/dist/signer.d.ts
CHANGED
|
@@ -9,6 +9,16 @@ export interface LocalLdgrWallet extends LdgrSigner {
|
|
|
9
9
|
/** Store this only in the caller's chosen secret manager; it is never sent to LDGR. */
|
|
10
10
|
readonly recoveryKey: string;
|
|
11
11
|
}
|
|
12
|
+
export interface LocalSilentPaymentIdentity {
|
|
13
|
+
readonly paymentCode: string;
|
|
14
|
+
readonly senderDhPublicKey: string;
|
|
15
|
+
readonly receiverScanPublicKey: string;
|
|
16
|
+
readonly spendPublicKey: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function deriveLocalSilentPaymentIdentity(input: {
|
|
19
|
+
readonly recoveryKey: string;
|
|
20
|
+
readonly ledgerId: LedgerId;
|
|
21
|
+
}): LocalSilentPaymentIdentity;
|
|
12
22
|
export declare function createWebCryptoSigner(input: {
|
|
13
23
|
readonly ledgerId: LedgerId;
|
|
14
24
|
readonly privateKey: CryptoKey;
|
|
@@ -21,4 +31,4 @@ export declare function generateLocalWallet(input: {
|
|
|
21
31
|
export declare function loadLocalWallet(input: {
|
|
22
32
|
readonly recoveryKey: string;
|
|
23
33
|
readonly ledgerId: LedgerId;
|
|
24
|
-
}): Promise<
|
|
34
|
+
}): Promise<LocalLdgrWallet>;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AssetBridgePolicy, LdgrNetwork } from "@ldgr/protocol";
|
|
2
|
+
import type { LocalLdgrWallet } from "./signer";
|
|
2
3
|
export type LedgerId = 0 | 1;
|
|
3
4
|
export type { LdgrNetwork } from "@ldgr/protocol";
|
|
4
5
|
export type LdgrTransaction = {
|
|
@@ -63,6 +64,34 @@ export type PaymentVerification = {
|
|
|
63
64
|
readonly status: "not_found" | "mismatch";
|
|
64
65
|
readonly transactionHash: string;
|
|
65
66
|
};
|
|
67
|
+
/** Immutable merchant order terms; amount is the gross exact decimal amount. */
|
|
68
|
+
export type MerchantPaymentExpectation = PaymentExpectation & {
|
|
69
|
+
readonly asset: string;
|
|
70
|
+
readonly reference: string;
|
|
71
|
+
};
|
|
72
|
+
export type CheckoutPaymentExpectation = MerchantPaymentExpectation & {
|
|
73
|
+
readonly transactionHash?: string;
|
|
74
|
+
};
|
|
75
|
+
/** Identifiers forwarded from a widget event, never trusted order terms. */
|
|
76
|
+
export type PaymentSubmission = {
|
|
77
|
+
readonly transactionHash: string;
|
|
78
|
+
readonly checkoutSessionId?: string;
|
|
79
|
+
};
|
|
80
|
+
export type CheckoutPaymentVerification = {
|
|
81
|
+
readonly status: "confirmed";
|
|
82
|
+
readonly sessionId: string;
|
|
83
|
+
readonly transactionHash: string;
|
|
84
|
+
readonly from: string;
|
|
85
|
+
readonly to: string;
|
|
86
|
+
readonly asset: string;
|
|
87
|
+
readonly amount: string;
|
|
88
|
+
readonly merchantAmount: string;
|
|
89
|
+
readonly checkoutFee: string;
|
|
90
|
+
} | {
|
|
91
|
+
readonly status: "pending" | "expired" | "not_found" | "mismatch";
|
|
92
|
+
readonly sessionId: string;
|
|
93
|
+
};
|
|
94
|
+
export type MerchantPaymentVerification = PaymentVerification | CheckoutPaymentVerification;
|
|
66
95
|
export type Ldgr402Verification = PaymentVerification | {
|
|
67
96
|
readonly status: "expired";
|
|
68
97
|
readonly transactionHash: string;
|
|
@@ -75,6 +104,58 @@ export type SignedSubmission = {
|
|
|
75
104
|
readonly feeSponsor?: unknown;
|
|
76
105
|
readonly submittedAtMs?: number;
|
|
77
106
|
};
|
|
107
|
+
export type WalletPublicKeyRegistration = {
|
|
108
|
+
readonly version: 1;
|
|
109
|
+
readonly action: "wallet_key_register";
|
|
110
|
+
readonly chain: "ldgr-v1";
|
|
111
|
+
readonly ledgerId: LedgerId;
|
|
112
|
+
readonly from: string;
|
|
113
|
+
readonly publicKey: string;
|
|
114
|
+
/** Domain-separated X25519 public key used only by silent-payment senders. */
|
|
115
|
+
readonly senderDhPublicKey?: string;
|
|
116
|
+
readonly timestamp: string;
|
|
117
|
+
readonly signature: string;
|
|
118
|
+
};
|
|
119
|
+
export type WalletPublicKeyRecord = {
|
|
120
|
+
readonly address: string;
|
|
121
|
+
readonly publicKey: string;
|
|
122
|
+
readonly senderDhPublicKey?: string;
|
|
123
|
+
};
|
|
124
|
+
export type SilentPaymentScanRecord = {
|
|
125
|
+
readonly sequence: string;
|
|
126
|
+
readonly transactionHash: string;
|
|
127
|
+
readonly from: string;
|
|
128
|
+
readonly senderDhPublicKey: string;
|
|
129
|
+
readonly action: "transfer" | "transfer_batch";
|
|
130
|
+
readonly asset: string;
|
|
131
|
+
readonly nonce: string;
|
|
132
|
+
readonly outputs: readonly {
|
|
133
|
+
readonly to: string;
|
|
134
|
+
readonly amountAtomic: string;
|
|
135
|
+
}[];
|
|
136
|
+
};
|
|
137
|
+
export type SilentPaymentScanPage = {
|
|
138
|
+
readonly records: readonly SilentPaymentScanRecord[];
|
|
139
|
+
/** Raw committed-sequence checkpoint, including pages with no matching records. */
|
|
140
|
+
readonly nextAfter: string;
|
|
141
|
+
readonly moreAvailable: boolean;
|
|
142
|
+
};
|
|
143
|
+
export type SilentPaymentSendInput = {
|
|
144
|
+
readonly wallet: LocalLdgrWallet;
|
|
145
|
+
readonly paymentCode: string;
|
|
146
|
+
/** Public native id (`t0`/`t1`) or wire asset code. */
|
|
147
|
+
readonly asset: string;
|
|
148
|
+
readonly amount: string;
|
|
149
|
+
readonly memo?: string;
|
|
150
|
+
readonly metadata?: import("@ldgr/protocol").JsonValue;
|
|
151
|
+
readonly now?: () => number;
|
|
152
|
+
/** Recomputed for every signed attempt because it binds the exact payload. */
|
|
153
|
+
readonly buildFeeSponsor?: (submission: Pick<SignedSubmission, "payload" | "signature">) => Promise<unknown>;
|
|
154
|
+
};
|
|
155
|
+
export declare class SilentPaymentSubmissionUnknownError extends Error {
|
|
156
|
+
readonly submission: SignedSubmission;
|
|
157
|
+
constructor(submission: SignedSubmission, cause?: unknown);
|
|
158
|
+
}
|
|
78
159
|
export type LdgrClientOptions = {
|
|
79
160
|
/** `testnet` / `0` or `mainnet` / `1`; required to use public native ids `t0` / `t1`. */
|
|
80
161
|
readonly network?: LdgrNetwork;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ldgr/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.5.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",
|