@gvnrdao/dh-sdk 0.0.309 → 0.0.312
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/browser/dist/browser.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +172 -5
- package/dist/index.mjs +168 -5
- package/dist/interfaces/chunks/config.i.d.ts +21 -0
- package/dist/modules/diamond-hands-sdk.d.ts +23 -0
- package/dist/utils/eip712-login.d.ts +13 -1
- package/dist/utils/server-session-store.d.ts +44 -0
- package/dist/utils/server-session.d.ts +37 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export type { BitcoinOperationsConfig, BitcoinNetwork, EnrichedBitcoinBalance, }
|
|
|
35
35
|
export type { SupportedStablecoinData } from './graphs/diamond-hands';
|
|
36
36
|
export { validateSDKConfig, validateServiceModeConfig, validateStandaloneModeConfig, isServiceModeConfig, isStandaloneModeConfig, } from './interfaces/chunks/config.i';
|
|
37
37
|
export { assertSafeServiceEndpoint } from './utils/service-endpoint-policy';
|
|
38
|
+
export { buildLoginDomain, buildSignedLoginPayload, LOGIN_TYPES, LOGIN_TYPES_WITH_AUDIENCE, } from './utils/eip712-login';
|
|
39
|
+
export type { DhServerLoginMessage, DhServerLoginPayload, } from './utils/eip712-login';
|
|
38
40
|
export { DEFAULT_LIT_NETWORK, VALID_LIT_NETWORKS, SDK_DEFAULTS, } from './constants/chunks/sdk-config';
|
|
39
41
|
export { ALL_CONTRACTS, ALL_DEPLOYMENTS, getContractsByNetwork, getDeploymentByNetwork, LOCALHOST_CONTRACTS, SEPOLIA_CONTRACTS, } from './constants/chunks/deployment-addresses';
|
|
40
42
|
export type { DeploymentContracts, DeploymentData, DeploymentLatestEnv, } from './constants/chunks/deployment-addresses';
|
package/dist/index.js
CHANGED
|
@@ -5574,6 +5574,8 @@ __export(src_exports, {
|
|
|
5574
5574
|
ErrorSeverity: () => ErrorSeverity,
|
|
5575
5575
|
EventHelpers: () => EventHelpers,
|
|
5576
5576
|
LOCALHOST_CONTRACTS: () => LOCALHOST_CONTRACTS,
|
|
5577
|
+
LOGIN_TYPES: () => LOGIN_TYPES,
|
|
5578
|
+
LOGIN_TYPES_WITH_AUDIENCE: () => LOGIN_TYPES_WITH_AUDIENCE,
|
|
5577
5579
|
LRUCache: () => LRUCache,
|
|
5578
5580
|
LoanCreator: () => LoanCreator,
|
|
5579
5581
|
LoanQuery: () => LoanQuery,
|
|
@@ -5596,6 +5598,8 @@ __export(src_exports, {
|
|
|
5596
5598
|
buildBtcExecuteEnvelope: () => buildBtcExecuteEnvelope,
|
|
5597
5599
|
buildInvalidateMessageHash: () => buildInvalidateMessageHash,
|
|
5598
5600
|
buildInvalidateStaleSpendEnvelope: () => buildInvalidateStaleSpendEnvelope,
|
|
5601
|
+
buildLoginDomain: () => buildLoginDomain,
|
|
5602
|
+
buildSignedLoginPayload: () => buildSignedLoginPayload,
|
|
5599
5603
|
collectFailures: () => collectFailures,
|
|
5600
5604
|
collectSuccesses: () => collectSuccesses,
|
|
5601
5605
|
combine: () => combine,
|
|
@@ -6527,12 +6531,12 @@ var LOGIN_TYPES_WITH_AUDIENCE = {
|
|
|
6527
6531
|
{ name: "audience", type: "string" }
|
|
6528
6532
|
]
|
|
6529
6533
|
};
|
|
6530
|
-
async function buildSignedLoginPayload(signer, chainId, audience) {
|
|
6534
|
+
async function buildSignedLoginPayload(signer, chainId, audience, reuse) {
|
|
6531
6535
|
const address = await signer.getAddress();
|
|
6532
6536
|
const message = {
|
|
6533
6537
|
address,
|
|
6534
|
-
issuedAt: Math.floor(Date.now() / 1e3),
|
|
6535
|
-
nonce: (0, import_ethers4.hexlify)((0, import_ethers4.randomBytes)(32)),
|
|
6538
|
+
issuedAt: reuse?.issuedAt ?? Math.floor(Date.now() / 1e3),
|
|
6539
|
+
nonce: reuse?.nonce ?? (0, import_ethers4.hexlify)((0, import_ethers4.randomBytes)(32)),
|
|
6536
6540
|
...audience ? { audience } : {}
|
|
6537
6541
|
};
|
|
6538
6542
|
const signature = await signer.signTypedData(
|
|
@@ -6550,6 +6554,13 @@ var STORE_PREFIX = "dh-server-session:";
|
|
|
6550
6554
|
function sessionStoreKey(address, chainId, endpoint) {
|
|
6551
6555
|
return STORE_PREFIX + `${address.trim().toLowerCase()}:${chainId}:${endpoint.replace(/\/+$/, "")}`;
|
|
6552
6556
|
}
|
|
6557
|
+
var PENDING_ENVELOPE_TTL_SECONDS = 8 * 60;
|
|
6558
|
+
function pendingEnvelopeKey(sessionKey) {
|
|
6559
|
+
return sessionKey + ":pending";
|
|
6560
|
+
}
|
|
6561
|
+
function isPendingEnvelopeShape(value) {
|
|
6562
|
+
return !!value && typeof value === "object" && typeof value.issuedAt === "number" && typeof value.nonce === "string";
|
|
6563
|
+
}
|
|
6553
6564
|
function isPayloadShape(value) {
|
|
6554
6565
|
return !!value && typeof value === "object" && typeof value.chainId === "number" && typeof value.signature === "string" && !!value.message && typeof value.message.address === "string" && typeof value.message.issuedAt === "number" && typeof value.message.nonce === "string";
|
|
6555
6566
|
}
|
|
@@ -6591,9 +6602,48 @@ var LocalStorageSessionStore = class {
|
|
|
6591
6602
|
} catch {
|
|
6592
6603
|
}
|
|
6593
6604
|
}
|
|
6605
|
+
loadPending(key) {
|
|
6606
|
+
let raw;
|
|
6607
|
+
try {
|
|
6608
|
+
raw = window.localStorage.getItem(pendingEnvelopeKey(key));
|
|
6609
|
+
} catch {
|
|
6610
|
+
return null;
|
|
6611
|
+
}
|
|
6612
|
+
if (!raw)
|
|
6613
|
+
return null;
|
|
6614
|
+
let parsed;
|
|
6615
|
+
try {
|
|
6616
|
+
parsed = JSON.parse(raw);
|
|
6617
|
+
} catch {
|
|
6618
|
+
this.clearPending(key);
|
|
6619
|
+
return null;
|
|
6620
|
+
}
|
|
6621
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
6622
|
+
if (!isPendingEnvelopeShape(parsed) || nowSec >= parsed.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
|
|
6623
|
+
this.clearPending(key);
|
|
6624
|
+
return null;
|
|
6625
|
+
}
|
|
6626
|
+
return parsed;
|
|
6627
|
+
}
|
|
6628
|
+
savePending(key, envelope) {
|
|
6629
|
+
try {
|
|
6630
|
+
window.localStorage.setItem(
|
|
6631
|
+
pendingEnvelopeKey(key),
|
|
6632
|
+
JSON.stringify(envelope)
|
|
6633
|
+
);
|
|
6634
|
+
} catch {
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
clearPending(key) {
|
|
6638
|
+
try {
|
|
6639
|
+
window.localStorage.removeItem(pendingEnvelopeKey(key));
|
|
6640
|
+
} catch {
|
|
6641
|
+
}
|
|
6642
|
+
}
|
|
6594
6643
|
};
|
|
6595
6644
|
var MemorySessionStore = class {
|
|
6596
6645
|
entries = /* @__PURE__ */ new Map();
|
|
6646
|
+
pending = /* @__PURE__ */ new Map();
|
|
6597
6647
|
load(key) {
|
|
6598
6648
|
return this.entries.get(key) ?? null;
|
|
6599
6649
|
}
|
|
@@ -6603,6 +6653,23 @@ var MemorySessionStore = class {
|
|
|
6603
6653
|
clear(key) {
|
|
6604
6654
|
this.entries.delete(key);
|
|
6605
6655
|
}
|
|
6656
|
+
loadPending(key) {
|
|
6657
|
+
const envelope = this.pending.get(key);
|
|
6658
|
+
if (!envelope)
|
|
6659
|
+
return null;
|
|
6660
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
6661
|
+
if (nowSec >= envelope.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
|
|
6662
|
+
this.pending.delete(key);
|
|
6663
|
+
return null;
|
|
6664
|
+
}
|
|
6665
|
+
return envelope;
|
|
6666
|
+
}
|
|
6667
|
+
savePending(key, envelope) {
|
|
6668
|
+
this.pending.set(key, envelope);
|
|
6669
|
+
}
|
|
6670
|
+
clearPending(key) {
|
|
6671
|
+
this.pending.delete(key);
|
|
6672
|
+
}
|
|
6606
6673
|
};
|
|
6607
6674
|
function localStorageUsable() {
|
|
6608
6675
|
try {
|
|
@@ -6637,9 +6704,11 @@ function isLoginRejection(error) {
|
|
|
6637
6704
|
var ServerSession = class {
|
|
6638
6705
|
signer;
|
|
6639
6706
|
serviceEndpoint;
|
|
6707
|
+
loginAudience;
|
|
6640
6708
|
chainId;
|
|
6641
6709
|
store;
|
|
6642
6710
|
onSignaturePrompt;
|
|
6711
|
+
onSignatureResolved;
|
|
6643
6712
|
now;
|
|
6644
6713
|
fetchImpl;
|
|
6645
6714
|
cached = null;
|
|
@@ -6666,9 +6735,14 @@ var ServerSession = class {
|
|
|
6666
6735
|
assertSafeServiceEndpoint(opts.serviceEndpoint);
|
|
6667
6736
|
this.signer = opts.signer;
|
|
6668
6737
|
this.serviceEndpoint = opts.serviceEndpoint.replace(/\/+$/, "");
|
|
6738
|
+
this.loginAudience = (opts.loginAudience ?? this.serviceEndpoint).replace(
|
|
6739
|
+
/\/+$/,
|
|
6740
|
+
""
|
|
6741
|
+
);
|
|
6669
6742
|
this.chainId = opts.chainId;
|
|
6670
6743
|
this.store = opts.persistSession === false ? null : opts.sessionStore ?? createDefaultSessionStore();
|
|
6671
6744
|
this.onSignaturePrompt = opts.onSignaturePrompt;
|
|
6745
|
+
this.onSignatureResolved = opts.onSignatureResolved;
|
|
6672
6746
|
this.now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
6673
6747
|
this.fetchImpl = opts.fetchImpl ?? ((...a) => fetch(...a));
|
|
6674
6748
|
}
|
|
@@ -6764,6 +6838,53 @@ var ServerSession = class {
|
|
|
6764
6838
|
);
|
|
6765
6839
|
}
|
|
6766
6840
|
}
|
|
6841
|
+
/**
|
|
6842
|
+
* Adopt a login envelope signed ELSEWHERE and exchange it for a session now.
|
|
6843
|
+
*
|
|
6844
|
+
* The browser frontend collects one wallet signature and spends it at both the
|
|
6845
|
+
* api and this service, so the envelope is minted outside the SDK. Without this
|
|
6846
|
+
* seam the SDK would mint its own — a second wallet prompt, which for a
|
|
6847
|
+
* multi-sig Safe is another full propose-and-confirm ceremony across owners.
|
|
6848
|
+
*
|
|
6849
|
+
* Must be called PROMPTLY after signing. Each server pins the nonce on FIRST
|
|
6850
|
+
* use and requires that first use to be fresh (±120s EOA, ±600s contract
|
|
6851
|
+
* wallet); the stores are independent, so a late hand-off is rejected as
|
|
6852
|
+
* `stale_first_use` even though the other service already accepted it. After a
|
|
6853
|
+
* successful first use, silent re-mint covers the next 24h.
|
|
6854
|
+
*
|
|
6855
|
+
* No-ops the prompt path entirely: on success the session is cached and
|
|
6856
|
+
* persisted exactly as a self-minted login would be.
|
|
6857
|
+
*/
|
|
6858
|
+
adoptLoginPayload(payload) {
|
|
6859
|
+
const gen = this.generation;
|
|
6860
|
+
const notCleared = () => gen === this.generation;
|
|
6861
|
+
const attempt = (async () => {
|
|
6862
|
+
if (payload?.chainId !== this.chainId) {
|
|
6863
|
+
throw new Error(
|
|
6864
|
+
`ServerSession: cannot adopt a login for chain ${payload?.chainId} \u2014 this session is on ${this.chainId}`
|
|
6865
|
+
);
|
|
6866
|
+
}
|
|
6867
|
+
const payloadAddress = payload.message?.address?.toLowerCase();
|
|
6868
|
+
const signerAddress = (await this.signer.getAddress()).toLowerCase();
|
|
6869
|
+
if (!payloadAddress || payloadAddress !== signerAddress) {
|
|
6870
|
+
throw new Error(
|
|
6871
|
+
"ServerSession: cannot adopt a login signed by a different address"
|
|
6872
|
+
);
|
|
6873
|
+
}
|
|
6874
|
+
const session = await this.login(payload);
|
|
6875
|
+
if (notCleared()) {
|
|
6876
|
+
this.persist(payload, session);
|
|
6877
|
+
this.cached = session;
|
|
6878
|
+
this.promptBackoff = null;
|
|
6879
|
+
}
|
|
6880
|
+
return session;
|
|
6881
|
+
})();
|
|
6882
|
+
this.inFlight = attempt;
|
|
6883
|
+
return attempt.then(() => void 0).finally(() => {
|
|
6884
|
+
if (notCleared())
|
|
6885
|
+
this.inFlight = null;
|
|
6886
|
+
});
|
|
6887
|
+
}
|
|
6767
6888
|
/**
|
|
6768
6889
|
* Resolve a session WITHOUT ever prompting for a wallet signature: a live
|
|
6769
6890
|
* cached token, else the persisted JWT (adopted if still fresh, otherwise
|
|
@@ -6830,17 +6951,31 @@ var ServerSession = class {
|
|
|
6830
6951
|
this.onSignaturePrompt?.();
|
|
6831
6952
|
let session;
|
|
6832
6953
|
try {
|
|
6954
|
+
const store = this.store;
|
|
6955
|
+
const storeKey = store ? this.lastStoreKey : null;
|
|
6956
|
+
const reuse = storeKey ? store.loadPending?.(storeKey) ?? void 0 : void 0;
|
|
6833
6957
|
const payload = await buildSignedLoginPayload(
|
|
6834
6958
|
this.signer,
|
|
6835
6959
|
this.chainId,
|
|
6836
|
-
this.
|
|
6960
|
+
this.loginAudience,
|
|
6961
|
+
reuse
|
|
6837
6962
|
);
|
|
6963
|
+
if (storeKey && !reuse) {
|
|
6964
|
+
store.savePending?.(storeKey, {
|
|
6965
|
+
issuedAt: payload.message.issuedAt,
|
|
6966
|
+
nonce: payload.message.nonce
|
|
6967
|
+
});
|
|
6968
|
+
}
|
|
6838
6969
|
session = await this.login(payload);
|
|
6839
6970
|
if (notCleared()) {
|
|
6840
6971
|
this.persist(payload, session);
|
|
6841
6972
|
this.cached = session;
|
|
6842
6973
|
}
|
|
6974
|
+
if (storeKey)
|
|
6975
|
+
store.clearPending?.(storeKey);
|
|
6976
|
+
this.onSignatureResolved?.(true);
|
|
6843
6977
|
} catch (error) {
|
|
6978
|
+
this.onSignatureResolved?.(false);
|
|
6844
6979
|
const failures = (this.promptBackoff?.failures ?? 0) + 1;
|
|
6845
6980
|
this.promptBackoff = {
|
|
6846
6981
|
failures,
|
|
@@ -16466,6 +16601,32 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
16466
16601
|
await this.serverSession.clearPersisted();
|
|
16467
16602
|
}
|
|
16468
16603
|
}
|
|
16604
|
+
/**
|
|
16605
|
+
* Establish the lit-ops-server session from a login envelope the CALLER already
|
|
16606
|
+
* had signed, instead of prompting the wallet for one.
|
|
16607
|
+
*
|
|
16608
|
+
* For a host that authenticates several services from one wallet signature (the
|
|
16609
|
+
* browser frontend signs once and spends the same envelope at the api and here),
|
|
16610
|
+
* this is what turns two wallet prompts into one — and for a multi-sig Safe each
|
|
16611
|
+
* prompt saved is a whole propose-and-confirm ceremony across owners.
|
|
16612
|
+
*
|
|
16613
|
+
* Call it PROMPTLY after signing: each server pins the nonce on first use and
|
|
16614
|
+
* requires that first use to be fresh (±120s EOA, ±600s contract wallet), and
|
|
16615
|
+
* the two servers' replay stores are independent, so a late hand-off is rejected
|
|
16616
|
+
* as stale even though the other service already accepted the same envelope.
|
|
16617
|
+
* Once accepted, silent re-mint covers the next 24h with no further prompts.
|
|
16618
|
+
*
|
|
16619
|
+
* Requires `loginAudience` to be configured to a value BOTH servers accept —
|
|
16620
|
+
* otherwise this service rejects the envelope as `audience_mismatch`.
|
|
16621
|
+
*
|
|
16622
|
+
* Throws if the exchange fails, so a caller can fall back to the prompt path.
|
|
16623
|
+
* No-op in standalone mode (no server session to prime).
|
|
16624
|
+
*/
|
|
16625
|
+
async primeServerSession(payload) {
|
|
16626
|
+
if (!this.serverSession)
|
|
16627
|
+
return;
|
|
16628
|
+
await this.serverSession.adoptLoginPayload(payload);
|
|
16629
|
+
}
|
|
16469
16630
|
/**
|
|
16470
16631
|
* Audit H-9: invalidate the LoanQuery cache so subsequent reads return
|
|
16471
16632
|
* the post-write state. We clear the entire loan-query cache (not just
|
|
@@ -16577,10 +16738,12 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
16577
16738
|
this.serverSession = new ServerSession({
|
|
16578
16739
|
signer: config.authSigner ?? config.contractSigner,
|
|
16579
16740
|
serviceEndpoint: config.serviceEndpoint,
|
|
16741
|
+
loginAudience: config.loginAudience,
|
|
16580
16742
|
chainId: config.chainId,
|
|
16581
16743
|
persistSession: config.sessionPersistence?.enabled,
|
|
16582
16744
|
sessionStore: config.sessionPersistence?.store,
|
|
16583
|
-
onSignaturePrompt: config.onSessionSignaturePrompt
|
|
16745
|
+
onSignaturePrompt: config.onSessionSignaturePrompt,
|
|
16746
|
+
onSignatureResolved: config.onSessionSignatureResolved
|
|
16584
16747
|
});
|
|
16585
16748
|
}
|
|
16586
16749
|
const contractManagerResult = createContractManager({
|
|
@@ -23786,6 +23949,8 @@ async function getPositionDelegate(positionId, provider, registryAddress) {
|
|
|
23786
23949
|
ErrorSeverity,
|
|
23787
23950
|
EventHelpers,
|
|
23788
23951
|
LOCALHOST_CONTRACTS,
|
|
23952
|
+
LOGIN_TYPES,
|
|
23953
|
+
LOGIN_TYPES_WITH_AUDIENCE,
|
|
23789
23954
|
LRUCache,
|
|
23790
23955
|
LoanCreator,
|
|
23791
23956
|
LoanQuery,
|
|
@@ -23808,6 +23973,8 @@ async function getPositionDelegate(positionId, provider, registryAddress) {
|
|
|
23808
23973
|
buildBtcExecuteEnvelope,
|
|
23809
23974
|
buildInvalidateMessageHash,
|
|
23810
23975
|
buildInvalidateStaleSpendEnvelope,
|
|
23976
|
+
buildLoginDomain,
|
|
23977
|
+
buildSignedLoginPayload,
|
|
23811
23978
|
collectFailures,
|
|
23812
23979
|
collectSuccesses,
|
|
23813
23980
|
combine,
|
package/dist/index.mjs
CHANGED
|
@@ -6446,12 +6446,12 @@ var LOGIN_TYPES_WITH_AUDIENCE = {
|
|
|
6446
6446
|
{ name: "audience", type: "string" }
|
|
6447
6447
|
]
|
|
6448
6448
|
};
|
|
6449
|
-
async function buildSignedLoginPayload(signer, chainId, audience) {
|
|
6449
|
+
async function buildSignedLoginPayload(signer, chainId, audience, reuse) {
|
|
6450
6450
|
const address = await signer.getAddress();
|
|
6451
6451
|
const message = {
|
|
6452
6452
|
address,
|
|
6453
|
-
issuedAt: Math.floor(Date.now() / 1e3),
|
|
6454
|
-
nonce: hexlify(randomBytes(32)),
|
|
6453
|
+
issuedAt: reuse?.issuedAt ?? Math.floor(Date.now() / 1e3),
|
|
6454
|
+
nonce: reuse?.nonce ?? hexlify(randomBytes(32)),
|
|
6455
6455
|
...audience ? { audience } : {}
|
|
6456
6456
|
};
|
|
6457
6457
|
const signature = await signer.signTypedData(
|
|
@@ -6469,6 +6469,13 @@ var STORE_PREFIX = "dh-server-session:";
|
|
|
6469
6469
|
function sessionStoreKey(address, chainId, endpoint) {
|
|
6470
6470
|
return STORE_PREFIX + `${address.trim().toLowerCase()}:${chainId}:${endpoint.replace(/\/+$/, "")}`;
|
|
6471
6471
|
}
|
|
6472
|
+
var PENDING_ENVELOPE_TTL_SECONDS = 8 * 60;
|
|
6473
|
+
function pendingEnvelopeKey(sessionKey) {
|
|
6474
|
+
return sessionKey + ":pending";
|
|
6475
|
+
}
|
|
6476
|
+
function isPendingEnvelopeShape(value) {
|
|
6477
|
+
return !!value && typeof value === "object" && typeof value.issuedAt === "number" && typeof value.nonce === "string";
|
|
6478
|
+
}
|
|
6472
6479
|
function isPayloadShape(value) {
|
|
6473
6480
|
return !!value && typeof value === "object" && typeof value.chainId === "number" && typeof value.signature === "string" && !!value.message && typeof value.message.address === "string" && typeof value.message.issuedAt === "number" && typeof value.message.nonce === "string";
|
|
6474
6481
|
}
|
|
@@ -6510,9 +6517,48 @@ var LocalStorageSessionStore = class {
|
|
|
6510
6517
|
} catch {
|
|
6511
6518
|
}
|
|
6512
6519
|
}
|
|
6520
|
+
loadPending(key) {
|
|
6521
|
+
let raw;
|
|
6522
|
+
try {
|
|
6523
|
+
raw = window.localStorage.getItem(pendingEnvelopeKey(key));
|
|
6524
|
+
} catch {
|
|
6525
|
+
return null;
|
|
6526
|
+
}
|
|
6527
|
+
if (!raw)
|
|
6528
|
+
return null;
|
|
6529
|
+
let parsed;
|
|
6530
|
+
try {
|
|
6531
|
+
parsed = JSON.parse(raw);
|
|
6532
|
+
} catch {
|
|
6533
|
+
this.clearPending(key);
|
|
6534
|
+
return null;
|
|
6535
|
+
}
|
|
6536
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
6537
|
+
if (!isPendingEnvelopeShape(parsed) || nowSec >= parsed.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
|
|
6538
|
+
this.clearPending(key);
|
|
6539
|
+
return null;
|
|
6540
|
+
}
|
|
6541
|
+
return parsed;
|
|
6542
|
+
}
|
|
6543
|
+
savePending(key, envelope) {
|
|
6544
|
+
try {
|
|
6545
|
+
window.localStorage.setItem(
|
|
6546
|
+
pendingEnvelopeKey(key),
|
|
6547
|
+
JSON.stringify(envelope)
|
|
6548
|
+
);
|
|
6549
|
+
} catch {
|
|
6550
|
+
}
|
|
6551
|
+
}
|
|
6552
|
+
clearPending(key) {
|
|
6553
|
+
try {
|
|
6554
|
+
window.localStorage.removeItem(pendingEnvelopeKey(key));
|
|
6555
|
+
} catch {
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
6513
6558
|
};
|
|
6514
6559
|
var MemorySessionStore = class {
|
|
6515
6560
|
entries = /* @__PURE__ */ new Map();
|
|
6561
|
+
pending = /* @__PURE__ */ new Map();
|
|
6516
6562
|
load(key) {
|
|
6517
6563
|
return this.entries.get(key) ?? null;
|
|
6518
6564
|
}
|
|
@@ -6522,6 +6568,23 @@ var MemorySessionStore = class {
|
|
|
6522
6568
|
clear(key) {
|
|
6523
6569
|
this.entries.delete(key);
|
|
6524
6570
|
}
|
|
6571
|
+
loadPending(key) {
|
|
6572
|
+
const envelope = this.pending.get(key);
|
|
6573
|
+
if (!envelope)
|
|
6574
|
+
return null;
|
|
6575
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
6576
|
+
if (nowSec >= envelope.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
|
|
6577
|
+
this.pending.delete(key);
|
|
6578
|
+
return null;
|
|
6579
|
+
}
|
|
6580
|
+
return envelope;
|
|
6581
|
+
}
|
|
6582
|
+
savePending(key, envelope) {
|
|
6583
|
+
this.pending.set(key, envelope);
|
|
6584
|
+
}
|
|
6585
|
+
clearPending(key) {
|
|
6586
|
+
this.pending.delete(key);
|
|
6587
|
+
}
|
|
6525
6588
|
};
|
|
6526
6589
|
function localStorageUsable() {
|
|
6527
6590
|
try {
|
|
@@ -6556,9 +6619,11 @@ function isLoginRejection(error) {
|
|
|
6556
6619
|
var ServerSession = class {
|
|
6557
6620
|
signer;
|
|
6558
6621
|
serviceEndpoint;
|
|
6622
|
+
loginAudience;
|
|
6559
6623
|
chainId;
|
|
6560
6624
|
store;
|
|
6561
6625
|
onSignaturePrompt;
|
|
6626
|
+
onSignatureResolved;
|
|
6562
6627
|
now;
|
|
6563
6628
|
fetchImpl;
|
|
6564
6629
|
cached = null;
|
|
@@ -6585,9 +6650,14 @@ var ServerSession = class {
|
|
|
6585
6650
|
assertSafeServiceEndpoint(opts.serviceEndpoint);
|
|
6586
6651
|
this.signer = opts.signer;
|
|
6587
6652
|
this.serviceEndpoint = opts.serviceEndpoint.replace(/\/+$/, "");
|
|
6653
|
+
this.loginAudience = (opts.loginAudience ?? this.serviceEndpoint).replace(
|
|
6654
|
+
/\/+$/,
|
|
6655
|
+
""
|
|
6656
|
+
);
|
|
6588
6657
|
this.chainId = opts.chainId;
|
|
6589
6658
|
this.store = opts.persistSession === false ? null : opts.sessionStore ?? createDefaultSessionStore();
|
|
6590
6659
|
this.onSignaturePrompt = opts.onSignaturePrompt;
|
|
6660
|
+
this.onSignatureResolved = opts.onSignatureResolved;
|
|
6591
6661
|
this.now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
6592
6662
|
this.fetchImpl = opts.fetchImpl ?? ((...a) => fetch(...a));
|
|
6593
6663
|
}
|
|
@@ -6683,6 +6753,53 @@ var ServerSession = class {
|
|
|
6683
6753
|
);
|
|
6684
6754
|
}
|
|
6685
6755
|
}
|
|
6756
|
+
/**
|
|
6757
|
+
* Adopt a login envelope signed ELSEWHERE and exchange it for a session now.
|
|
6758
|
+
*
|
|
6759
|
+
* The browser frontend collects one wallet signature and spends it at both the
|
|
6760
|
+
* api and this service, so the envelope is minted outside the SDK. Without this
|
|
6761
|
+
* seam the SDK would mint its own — a second wallet prompt, which for a
|
|
6762
|
+
* multi-sig Safe is another full propose-and-confirm ceremony across owners.
|
|
6763
|
+
*
|
|
6764
|
+
* Must be called PROMPTLY after signing. Each server pins the nonce on FIRST
|
|
6765
|
+
* use and requires that first use to be fresh (±120s EOA, ±600s contract
|
|
6766
|
+
* wallet); the stores are independent, so a late hand-off is rejected as
|
|
6767
|
+
* `stale_first_use` even though the other service already accepted it. After a
|
|
6768
|
+
* successful first use, silent re-mint covers the next 24h.
|
|
6769
|
+
*
|
|
6770
|
+
* No-ops the prompt path entirely: on success the session is cached and
|
|
6771
|
+
* persisted exactly as a self-minted login would be.
|
|
6772
|
+
*/
|
|
6773
|
+
adoptLoginPayload(payload) {
|
|
6774
|
+
const gen = this.generation;
|
|
6775
|
+
const notCleared = () => gen === this.generation;
|
|
6776
|
+
const attempt = (async () => {
|
|
6777
|
+
if (payload?.chainId !== this.chainId) {
|
|
6778
|
+
throw new Error(
|
|
6779
|
+
`ServerSession: cannot adopt a login for chain ${payload?.chainId} \u2014 this session is on ${this.chainId}`
|
|
6780
|
+
);
|
|
6781
|
+
}
|
|
6782
|
+
const payloadAddress = payload.message?.address?.toLowerCase();
|
|
6783
|
+
const signerAddress = (await this.signer.getAddress()).toLowerCase();
|
|
6784
|
+
if (!payloadAddress || payloadAddress !== signerAddress) {
|
|
6785
|
+
throw new Error(
|
|
6786
|
+
"ServerSession: cannot adopt a login signed by a different address"
|
|
6787
|
+
);
|
|
6788
|
+
}
|
|
6789
|
+
const session = await this.login(payload);
|
|
6790
|
+
if (notCleared()) {
|
|
6791
|
+
this.persist(payload, session);
|
|
6792
|
+
this.cached = session;
|
|
6793
|
+
this.promptBackoff = null;
|
|
6794
|
+
}
|
|
6795
|
+
return session;
|
|
6796
|
+
})();
|
|
6797
|
+
this.inFlight = attempt;
|
|
6798
|
+
return attempt.then(() => void 0).finally(() => {
|
|
6799
|
+
if (notCleared())
|
|
6800
|
+
this.inFlight = null;
|
|
6801
|
+
});
|
|
6802
|
+
}
|
|
6686
6803
|
/**
|
|
6687
6804
|
* Resolve a session WITHOUT ever prompting for a wallet signature: a live
|
|
6688
6805
|
* cached token, else the persisted JWT (adopted if still fresh, otherwise
|
|
@@ -6749,17 +6866,31 @@ var ServerSession = class {
|
|
|
6749
6866
|
this.onSignaturePrompt?.();
|
|
6750
6867
|
let session;
|
|
6751
6868
|
try {
|
|
6869
|
+
const store = this.store;
|
|
6870
|
+
const storeKey = store ? this.lastStoreKey : null;
|
|
6871
|
+
const reuse = storeKey ? store.loadPending?.(storeKey) ?? void 0 : void 0;
|
|
6752
6872
|
const payload = await buildSignedLoginPayload(
|
|
6753
6873
|
this.signer,
|
|
6754
6874
|
this.chainId,
|
|
6755
|
-
this.
|
|
6875
|
+
this.loginAudience,
|
|
6876
|
+
reuse
|
|
6756
6877
|
);
|
|
6878
|
+
if (storeKey && !reuse) {
|
|
6879
|
+
store.savePending?.(storeKey, {
|
|
6880
|
+
issuedAt: payload.message.issuedAt,
|
|
6881
|
+
nonce: payload.message.nonce
|
|
6882
|
+
});
|
|
6883
|
+
}
|
|
6757
6884
|
session = await this.login(payload);
|
|
6758
6885
|
if (notCleared()) {
|
|
6759
6886
|
this.persist(payload, session);
|
|
6760
6887
|
this.cached = session;
|
|
6761
6888
|
}
|
|
6889
|
+
if (storeKey)
|
|
6890
|
+
store.clearPending?.(storeKey);
|
|
6891
|
+
this.onSignatureResolved?.(true);
|
|
6762
6892
|
} catch (error) {
|
|
6893
|
+
this.onSignatureResolved?.(false);
|
|
6763
6894
|
const failures = (this.promptBackoff?.failures ?? 0) + 1;
|
|
6764
6895
|
this.promptBackoff = {
|
|
6765
6896
|
failures,
|
|
@@ -16395,6 +16526,32 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
16395
16526
|
await this.serverSession.clearPersisted();
|
|
16396
16527
|
}
|
|
16397
16528
|
}
|
|
16529
|
+
/**
|
|
16530
|
+
* Establish the lit-ops-server session from a login envelope the CALLER already
|
|
16531
|
+
* had signed, instead of prompting the wallet for one.
|
|
16532
|
+
*
|
|
16533
|
+
* For a host that authenticates several services from one wallet signature (the
|
|
16534
|
+
* browser frontend signs once and spends the same envelope at the api and here),
|
|
16535
|
+
* this is what turns two wallet prompts into one — and for a multi-sig Safe each
|
|
16536
|
+
* prompt saved is a whole propose-and-confirm ceremony across owners.
|
|
16537
|
+
*
|
|
16538
|
+
* Call it PROMPTLY after signing: each server pins the nonce on first use and
|
|
16539
|
+
* requires that first use to be fresh (±120s EOA, ±600s contract wallet), and
|
|
16540
|
+
* the two servers' replay stores are independent, so a late hand-off is rejected
|
|
16541
|
+
* as stale even though the other service already accepted the same envelope.
|
|
16542
|
+
* Once accepted, silent re-mint covers the next 24h with no further prompts.
|
|
16543
|
+
*
|
|
16544
|
+
* Requires `loginAudience` to be configured to a value BOTH servers accept —
|
|
16545
|
+
* otherwise this service rejects the envelope as `audience_mismatch`.
|
|
16546
|
+
*
|
|
16547
|
+
* Throws if the exchange fails, so a caller can fall back to the prompt path.
|
|
16548
|
+
* No-op in standalone mode (no server session to prime).
|
|
16549
|
+
*/
|
|
16550
|
+
async primeServerSession(payload) {
|
|
16551
|
+
if (!this.serverSession)
|
|
16552
|
+
return;
|
|
16553
|
+
await this.serverSession.adoptLoginPayload(payload);
|
|
16554
|
+
}
|
|
16398
16555
|
/**
|
|
16399
16556
|
* Audit H-9: invalidate the LoanQuery cache so subsequent reads return
|
|
16400
16557
|
* the post-write state. We clear the entire loan-query cache (not just
|
|
@@ -16506,10 +16663,12 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
16506
16663
|
this.serverSession = new ServerSession({
|
|
16507
16664
|
signer: config.authSigner ?? config.contractSigner,
|
|
16508
16665
|
serviceEndpoint: config.serviceEndpoint,
|
|
16666
|
+
loginAudience: config.loginAudience,
|
|
16509
16667
|
chainId: config.chainId,
|
|
16510
16668
|
persistSession: config.sessionPersistence?.enabled,
|
|
16511
16669
|
sessionStore: config.sessionPersistence?.store,
|
|
16512
|
-
onSignaturePrompt: config.onSessionSignaturePrompt
|
|
16670
|
+
onSignaturePrompt: config.onSessionSignaturePrompt,
|
|
16671
|
+
onSignatureResolved: config.onSessionSignatureResolved
|
|
16513
16672
|
});
|
|
16514
16673
|
}
|
|
16515
16674
|
const contractManagerResult = createContractManager({
|
|
@@ -23714,6 +23873,8 @@ export {
|
|
|
23714
23873
|
ErrorSeverity,
|
|
23715
23874
|
EventHelpers,
|
|
23716
23875
|
LOCALHOST_CONTRACTS,
|
|
23876
|
+
LOGIN_TYPES,
|
|
23877
|
+
LOGIN_TYPES_WITH_AUDIENCE,
|
|
23717
23878
|
LRUCache,
|
|
23718
23879
|
LoanCreator,
|
|
23719
23880
|
LoanQuery,
|
|
@@ -23736,6 +23897,8 @@ export {
|
|
|
23736
23897
|
buildBtcExecuteEnvelope,
|
|
23737
23898
|
buildInvalidateMessageHash,
|
|
23738
23899
|
buildInvalidateStaleSpendEnvelope,
|
|
23900
|
+
buildLoginDomain,
|
|
23901
|
+
buildSignedLoginPayload,
|
|
23739
23902
|
collectFailures,
|
|
23740
23903
|
collectSuccesses,
|
|
23741
23904
|
combine,
|
|
@@ -71,12 +71,33 @@ interface BaseSDKConfig {
|
|
|
71
71
|
/** Custom store (e.g. a file-backed store for CLI daemons). */
|
|
72
72
|
store?: ServerSessionStore;
|
|
73
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Audit M-8 audience the server-session login binds to. Defaults to
|
|
76
|
+
* `serviceEndpoint`.
|
|
77
|
+
*
|
|
78
|
+
* Set this only when ONE signature must authenticate several services — the
|
|
79
|
+
* browser frontend signs a single envelope and spends it at both
|
|
80
|
+
* lit-ops-server and the api, and a per-endpoint audience cannot name two
|
|
81
|
+
* endpoints. Every server the envelope is presented to must list this value as
|
|
82
|
+
* an accepted audience, so changing it requires a server-side config change
|
|
83
|
+
* FIRST. Node callers (CLI, MCP, cr-monitor) omit it and keep binding to the
|
|
84
|
+
* endpoint they call.
|
|
85
|
+
*/
|
|
86
|
+
loginAudience?: string;
|
|
74
87
|
/**
|
|
75
88
|
* Fires immediately before the server-session login requests a wallet
|
|
76
89
|
* signature — never on the silent re-mint paths. Lets UIs show a
|
|
77
90
|
* "check your wallet" prompt.
|
|
78
91
|
*/
|
|
79
92
|
onSessionSignaturePrompt?: () => void;
|
|
93
|
+
/**
|
|
94
|
+
* Fires once that signature request has concluded — `true` when a session was
|
|
95
|
+
* established, `false` when it failed or was rejected. Pairs with
|
|
96
|
+
* `onSessionSignaturePrompt`, which otherwise has no ending: a UI that shows
|
|
97
|
+
* "check your wallet" on the prompt has no way to stop showing it, and a
|
|
98
|
+
* contract wallet can leave that request open for minutes.
|
|
99
|
+
*/
|
|
100
|
+
onSessionSignatureResolved?: (ok: boolean) => void;
|
|
80
101
|
ethRpcUrl?: string;
|
|
81
102
|
chainId?: number;
|
|
82
103
|
networkOverride?: {
|
|
@@ -18,6 +18,7 @@ import { SDKError } from "../utils/error-handler";
|
|
|
18
18
|
import type { CreateLoanRequest, CreateLoanResult, LoanDataDetail, UCDMintRequest, UCDMintResult, PartialPaymentRequest, PartialPaymentResult, BTCWithdrawalResult, RenewPositionRequest, RenewPositionResult, LiquidationRequest, LiquidationResult, ConfirmBalanceRequest, ConfirmBalanceResult, TermsWithFeesResult } from "../interfaces/chunks/loan-operations.i";
|
|
19
19
|
import type { DiamondHandsSDKConfig } from "../interfaces/chunks/config.i";
|
|
20
20
|
import type { PKPData } from "../interfaces/chunks/pkp-integration.i";
|
|
21
|
+
import type { DhServerLoginPayload } from "../utils/eip712-login";
|
|
21
22
|
import { type ReconciledWithdrawal } from "../utils/withdrawal-reconciliation.utils";
|
|
22
23
|
import { ContractManager } from "./contract/contract-manager.module";
|
|
23
24
|
import { WithdrawalAddressModule } from "./withdrawal-address/withdrawal-address.module";
|
|
@@ -125,6 +126,28 @@ export declare class DiamondHandsSDK {
|
|
|
125
126
|
* and never prompts the wallet.
|
|
126
127
|
*/
|
|
127
128
|
clearServerSession(): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Establish the lit-ops-server session from a login envelope the CALLER already
|
|
131
|
+
* had signed, instead of prompting the wallet for one.
|
|
132
|
+
*
|
|
133
|
+
* For a host that authenticates several services from one wallet signature (the
|
|
134
|
+
* browser frontend signs once and spends the same envelope at the api and here),
|
|
135
|
+
* this is what turns two wallet prompts into one — and for a multi-sig Safe each
|
|
136
|
+
* prompt saved is a whole propose-and-confirm ceremony across owners.
|
|
137
|
+
*
|
|
138
|
+
* Call it PROMPTLY after signing: each server pins the nonce on first use and
|
|
139
|
+
* requires that first use to be fresh (±120s EOA, ±600s contract wallet), and
|
|
140
|
+
* the two servers' replay stores are independent, so a late hand-off is rejected
|
|
141
|
+
* as stale even though the other service already accepted the same envelope.
|
|
142
|
+
* Once accepted, silent re-mint covers the next 24h with no further prompts.
|
|
143
|
+
*
|
|
144
|
+
* Requires `loginAudience` to be configured to a value BOTH servers accept —
|
|
145
|
+
* otherwise this service rejects the envelope as `audience_mismatch`.
|
|
146
|
+
*
|
|
147
|
+
* Throws if the exchange fails, so a caller can fall back to the prompt path.
|
|
148
|
+
* No-op in standalone mode (no server session to prime).
|
|
149
|
+
*/
|
|
150
|
+
primeServerSession(payload: DhServerLoginPayload): Promise<void>;
|
|
128
151
|
/**
|
|
129
152
|
* Audit H-9: invalidate the LoanQuery cache so subsequent reads return
|
|
130
153
|
* the post-write state. We clear the entire loan-query cache (not just
|
|
@@ -31,4 +31,16 @@ export declare const LOGIN_TYPES_WITH_AUDIENCE: Record<string, TypedDataField[]>
|
|
|
31
31
|
* Audit M-8: pass `audience` (the service endpoint URL being logged into) to bind the login to
|
|
32
32
|
* one service. Omit it for legacy compatibility during the migration window.
|
|
33
33
|
*/
|
|
34
|
-
export declare function buildSignedLoginPayload(signer: Signer, chainId: number, audience?: string
|
|
34
|
+
export declare function buildSignedLoginPayload(signer: Signer, chainId: number, audience?: string,
|
|
35
|
+
/**
|
|
36
|
+
* Reuse the envelope of a login already collecting signatures instead of
|
|
37
|
+
* minting a fresh one. Every field is hashed into the EIP-712 message, so a
|
|
38
|
+
* new nonce produces a new hash — which a multi-owner Safe shows as a new
|
|
39
|
+
* message needing its FIRST signature, discarding the ones already collected.
|
|
40
|
+
* Passing the pending envelope back reproduces the same hash, so the next
|
|
41
|
+
* owner adds to it. See `server-session-store.ts`.
|
|
42
|
+
*/
|
|
43
|
+
reuse?: {
|
|
44
|
+
issuedAt: number;
|
|
45
|
+
nonce: string;
|
|
46
|
+
}): Promise<DhServerLoginPayload>;
|