@unifold/core 0.1.68 → 0.1.70-beta.1
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/dist/index.d.mts +484 -2
- package/dist/index.d.ts +484 -2
- package/dist/index.js +901 -2
- package/dist/index.mjs +888 -2
- package/package.json +6 -3
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
1
5
|
// src/lib/utils.ts
|
|
2
6
|
function formatStablecoinAmount(baseUnits, decimals) {
|
|
3
7
|
const raw = Number(baseUnits) / 10 ** decimals;
|
|
@@ -94,6 +98,16 @@ var ActionType = /* @__PURE__ */ ((ActionType2) => {
|
|
|
94
98
|
ActionType2["Withdraw"] = "withdraw";
|
|
95
99
|
return ActionType2;
|
|
96
100
|
})(ActionType || {});
|
|
101
|
+
var DepositAddressValidationError = class extends Error {
|
|
102
|
+
constructor(message) {
|
|
103
|
+
super(message);
|
|
104
|
+
__publicField(this, "isDepositAddressValidationError", true);
|
|
105
|
+
this.name = "DepositAddressValidationError";
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
function isDepositAddressValidationError(error) {
|
|
109
|
+
return error instanceof Error && error.isDepositAddressValidationError === true;
|
|
110
|
+
}
|
|
97
111
|
async function createDepositAddress(overrides, publishableKey) {
|
|
98
112
|
if (!overrides?.external_user_id) {
|
|
99
113
|
throw new Error("external_user_id is required");
|
|
@@ -121,6 +135,13 @@ async function createDepositAddress(overrides, publishableKey) {
|
|
|
121
135
|
body: JSON.stringify(payload)
|
|
122
136
|
});
|
|
123
137
|
if (!response.ok) {
|
|
138
|
+
if (response.status === 400) {
|
|
139
|
+
const body = await response.json().catch(() => null);
|
|
140
|
+
if (body?.error_type === "validation_error") {
|
|
141
|
+
const firstError = Array.isArray(body.details?.errors) ? body.details?.errors[0] : void 0;
|
|
142
|
+
throw new DepositAddressValidationError(firstError ?? "Invalid recipient address");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
124
145
|
throw new Error(`Failed to create EOA: ${response.statusText}`);
|
|
125
146
|
}
|
|
126
147
|
return response.json();
|
|
@@ -291,6 +312,27 @@ async function getFiatCurrencies(publishableKey) {
|
|
|
291
312
|
}
|
|
292
313
|
return response.json();
|
|
293
314
|
}
|
|
315
|
+
async function getFiatExchangeRates(options = {}, publishableKey) {
|
|
316
|
+
const pk = publishableKey || DEFAULT_PUBLISHABLE_KEY;
|
|
317
|
+
validatePublishableKey(pk);
|
|
318
|
+
const params = new URLSearchParams();
|
|
319
|
+
if (options.currencies && options.currencies.length > 0) {
|
|
320
|
+
params.set("currencies", options.currencies.join(","));
|
|
321
|
+
}
|
|
322
|
+
const queryString = params.toString();
|
|
323
|
+
const url = `${API_BASE_URL}/v1/public/exchange_rates/fiat_currencies${queryString ? `?${queryString}` : ""}`;
|
|
324
|
+
const response = await fetch(url, {
|
|
325
|
+
method: "GET",
|
|
326
|
+
headers: {
|
|
327
|
+
accept: "application/json",
|
|
328
|
+
"x-publishable-key": pk
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
if (!response.ok) {
|
|
332
|
+
throw new Error(`Failed to fetch fiat exchange rates: ${response.statusText}`);
|
|
333
|
+
}
|
|
334
|
+
return response.json();
|
|
335
|
+
}
|
|
294
336
|
async function getOnrampQuotes(request, publishableKey) {
|
|
295
337
|
const pk = publishableKey || DEFAULT_PUBLISHABLE_KEY;
|
|
296
338
|
validatePublishableKey(pk);
|
|
@@ -456,6 +498,21 @@ async function getProjectConfig(publishableKey, options) {
|
|
|
456
498
|
const data = await response.json();
|
|
457
499
|
return data;
|
|
458
500
|
}
|
|
501
|
+
async function getPublicIncident(publishableKey) {
|
|
502
|
+
const pk = publishableKey || DEFAULT_PUBLISHABLE_KEY;
|
|
503
|
+
validatePublishableKey(pk);
|
|
504
|
+
const response = await fetch(`${API_BASE_URL}/v1/public/projects/incident`, {
|
|
505
|
+
method: "GET",
|
|
506
|
+
headers: {
|
|
507
|
+
accept: "application/json",
|
|
508
|
+
"x-publishable-key": pk
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
if (!response.ok) {
|
|
512
|
+
throw new Error(`Failed to fetch public incident: ${response.statusText}`);
|
|
513
|
+
}
|
|
514
|
+
return response.json();
|
|
515
|
+
}
|
|
459
516
|
async function getIpAddress() {
|
|
460
517
|
const response = await fetch(`${API_BASE_URL}/v1/public/ip_address`, {
|
|
461
518
|
method: "GET",
|
|
@@ -511,7 +568,7 @@ async function getExternalWallets(publishableKey) {
|
|
|
511
568
|
const data = await response.json();
|
|
512
569
|
return data;
|
|
513
570
|
}
|
|
514
|
-
async function getWalletMobileDeepLink(wallet, depositAddresses, publishableKey) {
|
|
571
|
+
async function getWalletMobileDeepLink(wallet, depositAddresses, publishableKey, amountUsd) {
|
|
515
572
|
const pk = publishableKey || DEFAULT_PUBLISHABLE_KEY;
|
|
516
573
|
validatePublishableKey(pk);
|
|
517
574
|
const response = await fetch(`${API_BASE_URL}/v1/public/external_wallets/mobile_deeplink`, {
|
|
@@ -521,7 +578,11 @@ async function getWalletMobileDeepLink(wallet, depositAddresses, publishableKey)
|
|
|
521
578
|
accept: "application/json",
|
|
522
579
|
"x-publishable-key": pk
|
|
523
580
|
},
|
|
524
|
-
body: JSON.stringify({
|
|
581
|
+
body: JSON.stringify({
|
|
582
|
+
wallet,
|
|
583
|
+
deposit_addresses: depositAddresses,
|
|
584
|
+
...amountUsd ? { amount_usd: amountUsd } : {}
|
|
585
|
+
})
|
|
525
586
|
});
|
|
526
587
|
if (!response.ok) {
|
|
527
588
|
throw new Error(`Failed to generate wallet deep link: ${response.statusText}`);
|
|
@@ -566,6 +627,15 @@ async function verifyRecipientAddress(request, publishableKey) {
|
|
|
566
627
|
body: JSON.stringify(request)
|
|
567
628
|
});
|
|
568
629
|
if (!response.ok) {
|
|
630
|
+
const body = await response.json().catch(() => null);
|
|
631
|
+
if (response.status === 400 && body?.error_type === "validation_error") {
|
|
632
|
+
const firstError = Array.isArray(body.details?.errors) ? body.details?.errors[0] : void 0;
|
|
633
|
+
return {
|
|
634
|
+
valid: false,
|
|
635
|
+
failure_code: "validation_error",
|
|
636
|
+
message: firstError ?? "Invalid recipient address"
|
|
637
|
+
};
|
|
638
|
+
}
|
|
569
639
|
throw new Error(`Failed to verify recipient address: ${response.statusText}`);
|
|
570
640
|
}
|
|
571
641
|
return response.json();
|
|
@@ -989,6 +1059,7 @@ async function stripeGetDefaultToken(params, publishableKey) {
|
|
|
989
1059
|
chain_type: params.chainType
|
|
990
1060
|
});
|
|
991
1061
|
if (params.countryCode) query.append("country_code", params.countryCode);
|
|
1062
|
+
if (params.subdivisionCode) query.append("subdivision_code", params.subdivisionCode);
|
|
992
1063
|
const response = await fetch(
|
|
993
1064
|
`${API_BASE_URL}${HEADLESS_STRIPE_BASE}/default_token?${query.toString()}`,
|
|
994
1065
|
{
|
|
@@ -1514,6 +1585,806 @@ var CheckoutEventType = /* @__PURE__ */ ((CheckoutEventType2) => {
|
|
|
1514
1585
|
return CheckoutEventType2;
|
|
1515
1586
|
})(CheckoutEventType || {});
|
|
1516
1587
|
|
|
1588
|
+
// src/lib/emitter.ts
|
|
1589
|
+
var TypedEmitter = class {
|
|
1590
|
+
constructor() {
|
|
1591
|
+
__publicField(this, "handlers", /* @__PURE__ */ new Map());
|
|
1592
|
+
}
|
|
1593
|
+
on(type, handler) {
|
|
1594
|
+
let set = this.handlers.get(type);
|
|
1595
|
+
if (!set) {
|
|
1596
|
+
set = /* @__PURE__ */ new Set();
|
|
1597
|
+
this.handlers.set(type, set);
|
|
1598
|
+
}
|
|
1599
|
+
set.add(handler);
|
|
1600
|
+
return () => {
|
|
1601
|
+
set.delete(handler);
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
emit(type, event) {
|
|
1605
|
+
const dispatch = (handler) => {
|
|
1606
|
+
try {
|
|
1607
|
+
handler(event);
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
console.error("[unifold] event handler threw", error);
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
this.handlers.get(type)?.forEach(dispatch);
|
|
1613
|
+
this.handlers.get("*")?.forEach(dispatch);
|
|
1614
|
+
}
|
|
1615
|
+
removeAllListeners() {
|
|
1616
|
+
this.handlers.clear();
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1620
|
+
// src/lib/mappers.ts
|
|
1621
|
+
function mapWalletToDepositAddress(wallet) {
|
|
1622
|
+
return {
|
|
1623
|
+
id: wallet.id,
|
|
1624
|
+
chainType: wallet.chain_type,
|
|
1625
|
+
addressType: wallet.address_type,
|
|
1626
|
+
address: wallet.address,
|
|
1627
|
+
destinationChainType: wallet.destination_chain_type,
|
|
1628
|
+
destinationChainId: wallet.destination_chain_id,
|
|
1629
|
+
destinationTokenAddress: wallet.destination_token_address,
|
|
1630
|
+
recipientAddress: wallet.recipient_address,
|
|
1631
|
+
isPrimary: wallet.is_primary
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
function mapDirectExecution(execution) {
|
|
1635
|
+
return {
|
|
1636
|
+
id: execution.id,
|
|
1637
|
+
transactionHash: execution.transaction_hash,
|
|
1638
|
+
recipientAddress: execution.recipient_address,
|
|
1639
|
+
depositAddress: execution.deposit_wallet?.address,
|
|
1640
|
+
sourceChainType: execution.source_chain_type,
|
|
1641
|
+
sourceChainId: execution.source_chain_id,
|
|
1642
|
+
sourceTokenAddress: execution.source_token_address,
|
|
1643
|
+
destinationChainType: execution.destination_chain_type,
|
|
1644
|
+
destinationChainId: execution.destination_chain_id,
|
|
1645
|
+
destinationTokenAddress: execution.destination_token_address,
|
|
1646
|
+
sourceAmountBaseUnit: execution.source_amount_base_unit,
|
|
1647
|
+
sourceAmountUsd: execution.source_amount_usd,
|
|
1648
|
+
destinationAmountBaseUnit: execution.destination_amount_base_unit,
|
|
1649
|
+
destinationAmountUsd: execution.destination_amount_usd,
|
|
1650
|
+
destinationTransactionHashes: execution.destination_transaction_hashes,
|
|
1651
|
+
status: execution.status,
|
|
1652
|
+
failureReason: execution.failure_reason,
|
|
1653
|
+
createdAt: execution.created_at,
|
|
1654
|
+
updatedAt: execution.updated_at,
|
|
1655
|
+
explorerUrl: execution.explorer_url,
|
|
1656
|
+
destinationExplorerUrl: execution.destination_explorer_url,
|
|
1657
|
+
sourceTokenMetadata: execution.source_token_metadata ? {
|
|
1658
|
+
iconUrl: execution.source_token_metadata.icon_url,
|
|
1659
|
+
iconUrls: execution.source_token_metadata.icon_urls,
|
|
1660
|
+
decimals: execution.source_token_metadata.decimals
|
|
1661
|
+
} : void 0,
|
|
1662
|
+
destinationTokenMetadata: execution.destination_token_metadata ? {
|
|
1663
|
+
iconUrl: execution.destination_token_metadata.icon_url,
|
|
1664
|
+
iconUrls: execution.destination_token_metadata.icon_urls,
|
|
1665
|
+
decimals: execution.destination_token_metadata.decimals
|
|
1666
|
+
} : void 0
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// src/lib/deposit-session.ts
|
|
1671
|
+
var DETECTION_POLL_INTERVAL_MS = 2500;
|
|
1672
|
+
var SCAN_NUDGE_INTERVAL_MS = 5e3;
|
|
1673
|
+
var DETECTION_ARM_DELAY_MS = 5e3;
|
|
1674
|
+
var LOOKBACK_MS = 6e4;
|
|
1675
|
+
var ADDRESS_CREATE_MAX_ATTEMPTS = 4;
|
|
1676
|
+
var DepositSessionEventType = /* @__PURE__ */ ((DepositSessionEventType2) => {
|
|
1677
|
+
DepositSessionEventType2["SESSION_STARTED"] = "deposit_session.started";
|
|
1678
|
+
DepositSessionEventType2["ADDRESSES_CREATED"] = "deposit_session.addresses_created";
|
|
1679
|
+
DepositSessionEventType2["CONFIRMATION_STARTED"] = "deposit_session.confirmation_started";
|
|
1680
|
+
DepositSessionEventType2["SESSION_STOPPED"] = "deposit_session.stopped";
|
|
1681
|
+
DepositSessionEventType2["SESSION_ERRORED"] = "deposit_session.errored";
|
|
1682
|
+
DepositSessionEventType2["EXECUTION_DETECTED"] = "direct_execution.detected";
|
|
1683
|
+
DepositSessionEventType2["EXECUTION_UPDATED"] = "direct_execution.updated";
|
|
1684
|
+
DepositSessionEventType2["EXECUTION_SUCCEEDED"] = "direct_execution.succeeded";
|
|
1685
|
+
DepositSessionEventType2["EXECUTION_FAILED"] = "direct_execution.failed";
|
|
1686
|
+
return DepositSessionEventType2;
|
|
1687
|
+
})(DepositSessionEventType || {});
|
|
1688
|
+
var IN_PROGRESS_STATUSES = [
|
|
1689
|
+
"pending" /* PENDING */,
|
|
1690
|
+
"waiting" /* WAITING */,
|
|
1691
|
+
"delayed" /* DELAYED */
|
|
1692
|
+
];
|
|
1693
|
+
var FAILURE_STATUSES = ["failed" /* FAILED */, "refunded" /* REFUNDED */];
|
|
1694
|
+
var DepositSessionWaitError = class extends Error {
|
|
1695
|
+
constructor(code, message, cause) {
|
|
1696
|
+
super(message);
|
|
1697
|
+
__publicField(this, "code");
|
|
1698
|
+
/** Failed execution (`DEPOSIT_FAILED`) or fatal {@link DepositSessionError} (`SESSION_ERROR`). */
|
|
1699
|
+
__publicField(this, "cause");
|
|
1700
|
+
this.name = "DepositSessionWaitError";
|
|
1701
|
+
this.code = code;
|
|
1702
|
+
this.cause = cause;
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
var SessionCheckError = class extends Error {
|
|
1706
|
+
constructor(code, message) {
|
|
1707
|
+
super(message);
|
|
1708
|
+
this.code = code;
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1712
|
+
var DepositSession = class {
|
|
1713
|
+
constructor(config) {
|
|
1714
|
+
/** Immutable id for correlation, `dsess_<ksuid>`. Client-generated. */
|
|
1715
|
+
__publicField(this, "id");
|
|
1716
|
+
__publicField(this, "emitter", new TypedEmitter());
|
|
1717
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
1718
|
+
__publicField(this, "publishableKey");
|
|
1719
|
+
__publicField(this, "externalUserId");
|
|
1720
|
+
__publicField(this, "destination");
|
|
1721
|
+
__publicField(this, "confirmationMode");
|
|
1722
|
+
__publicField(this, "method");
|
|
1723
|
+
// Run state. runToken invalidates in-flight async work across stop()/restart.
|
|
1724
|
+
__publicField(this, "runToken", 0);
|
|
1725
|
+
__publicField(this, "startPromise", null);
|
|
1726
|
+
__publicField(this, "destroyed", false);
|
|
1727
|
+
/** Pending waiter rejections, invoked by destroy() so waiters never hang. */
|
|
1728
|
+
__publicField(this, "waiterDestroyCallbacks", /* @__PURE__ */ new Set());
|
|
1729
|
+
__publicField(this, "baselineMs", 0);
|
|
1730
|
+
__publicField(this, "tracked", /* @__PURE__ */ new Map());
|
|
1731
|
+
__publicField(this, "pollErrorLatched", false);
|
|
1732
|
+
__publicField(this, "detectionTimer", null);
|
|
1733
|
+
__publicField(this, "nudgeTimer", null);
|
|
1734
|
+
__publicField(this, "armTimer", null);
|
|
1735
|
+
// Snapshot state
|
|
1736
|
+
__publicField(this, "status", "idle");
|
|
1737
|
+
__publicField(this, "addresses", []);
|
|
1738
|
+
__publicField(this, "addressIds", []);
|
|
1739
|
+
__publicField(this, "executions", []);
|
|
1740
|
+
__publicField(this, "checkingDeposit", false);
|
|
1741
|
+
__publicField(this, "error", null);
|
|
1742
|
+
__publicField(this, "snapshot");
|
|
1743
|
+
if (!config.publishableKey || config.publishableKey.trim() === "") {
|
|
1744
|
+
throw new Error("DepositSession: publishableKey is required");
|
|
1745
|
+
}
|
|
1746
|
+
if (!config.externalUserId) {
|
|
1747
|
+
throw new Error("DepositSession: externalUserId is required");
|
|
1748
|
+
}
|
|
1749
|
+
this.id = generatePrefixedKSUID("dsess");
|
|
1750
|
+
this.publishableKey = config.publishableKey;
|
|
1751
|
+
this.externalUserId = config.externalUserId;
|
|
1752
|
+
this.destination = config.destination;
|
|
1753
|
+
this.confirmationMode = config.confirmationMode ?? "auto";
|
|
1754
|
+
this.method = config.method ?? "transfer";
|
|
1755
|
+
this.snapshot = this.buildSnapshot();
|
|
1756
|
+
}
|
|
1757
|
+
// -- Public surface -------------------------------------------------------
|
|
1758
|
+
/** Synchronous snapshot; the reference is stable until state changes. */
|
|
1759
|
+
getSnapshot() {
|
|
1760
|
+
return this.snapshot;
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Subscribe to snapshot changes (external-store contract; drives
|
|
1764
|
+
* `useSyncExternalStore` in the React binding). Returns an unsubscribe fn.
|
|
1765
|
+
*/
|
|
1766
|
+
subscribe(listener) {
|
|
1767
|
+
this.listeners.add(listener);
|
|
1768
|
+
return () => {
|
|
1769
|
+
this.listeners.delete(listener);
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
on(type, handler) {
|
|
1773
|
+
return this.emitter.on(type, handler);
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Creates/fetches addresses (with a fail-fast recipient check) and starts
|
|
1777
|
+
* detection polling. Idempotent while running; callable again after stop()
|
|
1778
|
+
* or a fatal error (fresh baseline).
|
|
1779
|
+
*/
|
|
1780
|
+
start() {
|
|
1781
|
+
if (this.destroyed) {
|
|
1782
|
+
return Promise.reject(new Error("DepositSession has been destroyed"));
|
|
1783
|
+
}
|
|
1784
|
+
if (this.startPromise) return this.startPromise;
|
|
1785
|
+
this.startPromise = this.run();
|
|
1786
|
+
return this.startPromise;
|
|
1787
|
+
}
|
|
1788
|
+
/** Arms the backend scan nudge in 'manual' mode. No-op if already armed. */
|
|
1789
|
+
confirmFundsSent() {
|
|
1790
|
+
if (this.destroyed || !this.startPromise) return;
|
|
1791
|
+
this.armConfirmation("manual");
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Stops all polling. The session can be restarted with start(), which
|
|
1795
|
+
* resets the baseline and tracked executions (fresh run).
|
|
1796
|
+
*/
|
|
1797
|
+
stop() {
|
|
1798
|
+
const wasActive = this.startPromise !== null;
|
|
1799
|
+
this.runToken += 1;
|
|
1800
|
+
this.clearTimers();
|
|
1801
|
+
this.startPromise = null;
|
|
1802
|
+
if (this.checkingDeposit) {
|
|
1803
|
+
this.checkingDeposit = false;
|
|
1804
|
+
}
|
|
1805
|
+
if (wasActive && !this.destroyed) {
|
|
1806
|
+
this.commit();
|
|
1807
|
+
this.emitSessionEvent("deposit_session.stopped" /* SESSION_STOPPED */, {
|
|
1808
|
+
sessionId: this.id
|
|
1809
|
+
});
|
|
1810
|
+
this.notify();
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
/** stop() + release all listeners. Terminal — start() rejects afterwards. */
|
|
1814
|
+
destroy() {
|
|
1815
|
+
this.stop();
|
|
1816
|
+
this.destroyed = true;
|
|
1817
|
+
Array.from(this.waiterDestroyCallbacks).forEach((callback) => callback());
|
|
1818
|
+
this.waiterDestroyCallbacks.clear();
|
|
1819
|
+
this.emitter.removeAllListeners();
|
|
1820
|
+
this.listeners.clear();
|
|
1821
|
+
}
|
|
1822
|
+
// -- Promise waiters (subscription sugar over the event stream) ------------
|
|
1823
|
+
/**
|
|
1824
|
+
* Resolve when the session reaches one of the given statuses (immediately
|
|
1825
|
+
* if it's already there). Generic primitive over the status state machine:
|
|
1826
|
+
* `waitForStatus('processing')` awaits detection; `waitForStatus(['succeeded',
|
|
1827
|
+
* 'failed'])` awaits a terminal outcome.
|
|
1828
|
+
*
|
|
1829
|
+
* Rejects with {@link DepositSessionWaitError} on abort or destroy().
|
|
1830
|
+
* Does not start or stop the session — it only listens.
|
|
1831
|
+
*/
|
|
1832
|
+
waitForStatus(status, options = {}) {
|
|
1833
|
+
const statuses = Array.isArray(status) ? status : [status];
|
|
1834
|
+
return new Promise((resolve, reject) => {
|
|
1835
|
+
this.installWaiter({
|
|
1836
|
+
options,
|
|
1837
|
+
reject,
|
|
1838
|
+
subscribe: (settle) => {
|
|
1839
|
+
const check = () => {
|
|
1840
|
+
if (statuses.includes(this.snapshot.status)) settle(() => resolve(this.snapshot));
|
|
1841
|
+
};
|
|
1842
|
+
check();
|
|
1843
|
+
return this.subscribe(check);
|
|
1844
|
+
}
|
|
1845
|
+
});
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Resolve with the **first** succeeded {@link DirectExecution} observed by
|
|
1850
|
+
* this session — the one-liner for the 90% case. Mirrors `beginDeposit()`'s
|
|
1851
|
+
* promise contract: resolve on success, reject on failure.
|
|
1852
|
+
*
|
|
1853
|
+
* Multi-execution semantics (unlike quote-scoped models such as Privy's,
|
|
1854
|
+
* one session can observe many executions — a user may send twice, or on
|
|
1855
|
+
* two chains): this waiter is one-shot "first completion" detection. If an
|
|
1856
|
+
* execution has already succeeded, it resolves immediately with that
|
|
1857
|
+
* execution. The session keeps polling after success — to react to every
|
|
1858
|
+
* settlement, subscribe to `direct_execution.succeeded` events or read
|
|
1859
|
+
* `snapshot.executions` instead.
|
|
1860
|
+
*
|
|
1861
|
+
* Rejects with {@link DepositSessionWaitError}:
|
|
1862
|
+
* - `DEPOSIT_FAILED` (cause: the failed execution) when a deposit fails
|
|
1863
|
+
* before any succeeds,
|
|
1864
|
+
* - `SESSION_ERROR` (cause: the fatal {@link DepositSessionError}) on fatal
|
|
1865
|
+
* session errors (e.g. address creation failed),
|
|
1866
|
+
* - `ABORTED` / `DESTROYED` per the wait options and session lifecycle.
|
|
1867
|
+
*/
|
|
1868
|
+
waitForSuccess(options = {}) {
|
|
1869
|
+
return new Promise((resolve, reject) => {
|
|
1870
|
+
this.installWaiter({
|
|
1871
|
+
options,
|
|
1872
|
+
reject,
|
|
1873
|
+
subscribe: (settle) => {
|
|
1874
|
+
const { executions, error } = this.snapshot;
|
|
1875
|
+
const alreadySucceeded = executions.find(
|
|
1876
|
+
(execution) => execution.status === "succeeded" /* SUCCEEDED */
|
|
1877
|
+
);
|
|
1878
|
+
if (alreadySucceeded) {
|
|
1879
|
+
settle(() => resolve(alreadySucceeded));
|
|
1880
|
+
return () => {
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
const alreadyFailed = executions.find(
|
|
1884
|
+
(execution) => FAILURE_STATUSES.includes(execution.status)
|
|
1885
|
+
);
|
|
1886
|
+
if (alreadyFailed) {
|
|
1887
|
+
settle(
|
|
1888
|
+
() => reject(
|
|
1889
|
+
new DepositSessionWaitError("DEPOSIT_FAILED", "Deposit failed", alreadyFailed)
|
|
1890
|
+
)
|
|
1891
|
+
);
|
|
1892
|
+
return () => {
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
if (error?.fatal) {
|
|
1896
|
+
settle(() => reject(new DepositSessionWaitError("SESSION_ERROR", error.message, error)));
|
|
1897
|
+
return () => {
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
const offs = [
|
|
1901
|
+
this.on(
|
|
1902
|
+
"direct_execution.succeeded" /* EXECUTION_SUCCEEDED */,
|
|
1903
|
+
(event) => settle(() => resolve(event.data.object))
|
|
1904
|
+
),
|
|
1905
|
+
this.on(
|
|
1906
|
+
"direct_execution.failed" /* EXECUTION_FAILED */,
|
|
1907
|
+
(event) => settle(
|
|
1908
|
+
() => reject(
|
|
1909
|
+
new DepositSessionWaitError("DEPOSIT_FAILED", "Deposit failed", event.data.object)
|
|
1910
|
+
)
|
|
1911
|
+
)
|
|
1912
|
+
),
|
|
1913
|
+
this.on("deposit_session.errored" /* SESSION_ERRORED */, (event) => {
|
|
1914
|
+
if (!event.data.object.fatal) return;
|
|
1915
|
+
settle(
|
|
1916
|
+
() => reject(
|
|
1917
|
+
new DepositSessionWaitError(
|
|
1918
|
+
"SESSION_ERROR",
|
|
1919
|
+
event.data.object.message,
|
|
1920
|
+
event.data.object
|
|
1921
|
+
)
|
|
1922
|
+
)
|
|
1923
|
+
);
|
|
1924
|
+
})
|
|
1925
|
+
];
|
|
1926
|
+
return () => offs.forEach((off) => off());
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Shared waiter plumbing: AbortSignal and destroy() rejection, with
|
|
1933
|
+
* single-settlement and cleanup. `subscribe` installs the wait condition
|
|
1934
|
+
* and returns its unsubscribe fn; it settles via `settle(fn)`.
|
|
1935
|
+
*/
|
|
1936
|
+
installWaiter({
|
|
1937
|
+
options,
|
|
1938
|
+
reject,
|
|
1939
|
+
subscribe
|
|
1940
|
+
}) {
|
|
1941
|
+
if (this.destroyed) {
|
|
1942
|
+
reject(new DepositSessionWaitError("DESTROYED", "DepositSession has been destroyed"));
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
if (options.signal?.aborted) {
|
|
1946
|
+
reject(new DepositSessionWaitError("ABORTED", "Wait aborted", options.signal.reason));
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
let settled = false;
|
|
1950
|
+
let unsubscribe = null;
|
|
1951
|
+
const cleanup = () => {
|
|
1952
|
+
unsubscribe?.();
|
|
1953
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
1954
|
+
this.waiterDestroyCallbacks.delete(onDestroy);
|
|
1955
|
+
};
|
|
1956
|
+
const settle = (finish) => {
|
|
1957
|
+
if (settled) return;
|
|
1958
|
+
settled = true;
|
|
1959
|
+
cleanup();
|
|
1960
|
+
finish();
|
|
1961
|
+
};
|
|
1962
|
+
const onAbort = () => settle(
|
|
1963
|
+
() => reject(new DepositSessionWaitError("ABORTED", "Wait aborted", options.signal?.reason))
|
|
1964
|
+
);
|
|
1965
|
+
const onDestroy = () => settle(
|
|
1966
|
+
() => reject(new DepositSessionWaitError("DESTROYED", "DepositSession was destroyed while waiting"))
|
|
1967
|
+
);
|
|
1968
|
+
this.waiterDestroyCallbacks.add(onDestroy);
|
|
1969
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1970
|
+
unsubscribe = subscribe(settle);
|
|
1971
|
+
if (settled) cleanup();
|
|
1972
|
+
}
|
|
1973
|
+
// -- Run lifecycle ---------------------------------------------------------
|
|
1974
|
+
async run() {
|
|
1975
|
+
const token = ++this.runToken;
|
|
1976
|
+
this.baselineMs = Date.now();
|
|
1977
|
+
this.tracked.clear();
|
|
1978
|
+
this.pollErrorLatched = false;
|
|
1979
|
+
this.executions = [];
|
|
1980
|
+
this.error = null;
|
|
1981
|
+
this.checkingDeposit = false;
|
|
1982
|
+
this.setStatus("creating_addresses");
|
|
1983
|
+
this.emitSessionEvent("deposit_session.started" /* SESSION_STARTED */, {
|
|
1984
|
+
sessionId: this.id
|
|
1985
|
+
});
|
|
1986
|
+
this.notify();
|
|
1987
|
+
let wallets;
|
|
1988
|
+
try {
|
|
1989
|
+
[wallets] = await Promise.all([
|
|
1990
|
+
this.createAddressesWithRetry(token),
|
|
1991
|
+
this.runStartChecks()
|
|
1992
|
+
]);
|
|
1993
|
+
} catch (cause) {
|
|
1994
|
+
if (token !== this.runToken) return;
|
|
1995
|
+
const isCheck = cause instanceof SessionCheckError;
|
|
1996
|
+
this.failFatally(
|
|
1997
|
+
isCheck ? cause.code : "ADDRESS_CREATION_FAILED",
|
|
1998
|
+
isCheck ? cause.message : "Failed to create deposit addresses",
|
|
1999
|
+
cause
|
|
2000
|
+
);
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
if (token !== this.runToken) return;
|
|
2004
|
+
this.addresses = wallets.map(mapWalletToDepositAddress);
|
|
2005
|
+
this.addressIds = wallets.map((w) => w.id).filter(Boolean);
|
|
2006
|
+
this.setStatus("awaiting_funds");
|
|
2007
|
+
this.commit();
|
|
2008
|
+
this.emitSessionEvent("deposit_session.addresses_created" /* ADDRESSES_CREATED */, {
|
|
2009
|
+
sessionId: this.id,
|
|
2010
|
+
addresses: this.addresses
|
|
2011
|
+
});
|
|
2012
|
+
this.notify();
|
|
2013
|
+
this.startDetectionLoop(token);
|
|
2014
|
+
if (this.confirmationMode === "auto") {
|
|
2015
|
+
this.armTimer = setTimeout(() => {
|
|
2016
|
+
if (token === this.runToken) this.armConfirmation("auto");
|
|
2017
|
+
}, DETECTION_ARM_DELAY_MS);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
failFatally(code, message, cause) {
|
|
2021
|
+
this.clearTimers();
|
|
2022
|
+
this.startPromise = null;
|
|
2023
|
+
this.error = { code, message, fatal: true, cause };
|
|
2024
|
+
this.setStatus("error");
|
|
2025
|
+
this.commit();
|
|
2026
|
+
this.emitSessionEvent("deposit_session.errored" /* SESSION_ERRORED */, {
|
|
2027
|
+
sessionId: this.id,
|
|
2028
|
+
code,
|
|
2029
|
+
message,
|
|
2030
|
+
fatal: true
|
|
2031
|
+
});
|
|
2032
|
+
this.notify();
|
|
2033
|
+
}
|
|
2034
|
+
async createAddressesWithRetry(token) {
|
|
2035
|
+
let lastError;
|
|
2036
|
+
for (let attempt = 0; attempt < ADDRESS_CREATE_MAX_ATTEMPTS; attempt++) {
|
|
2037
|
+
if (attempt > 0) {
|
|
2038
|
+
await delay(Math.min(1e3 * 2 ** (attempt - 1), 1e4));
|
|
2039
|
+
if (token !== this.runToken) throw new Error("DepositSession stopped");
|
|
2040
|
+
}
|
|
2041
|
+
try {
|
|
2042
|
+
const response = await createDepositAddress(
|
|
2043
|
+
{
|
|
2044
|
+
external_user_id: this.externalUserId,
|
|
2045
|
+
destination_chain_type: this.destination.chainType,
|
|
2046
|
+
destination_chain_id: this.destination.chainId,
|
|
2047
|
+
destination_token_address: this.destination.tokenAddress,
|
|
2048
|
+
recipient_address: this.destination.recipientAddress,
|
|
2049
|
+
contract_calls: this.destination.contractCalls
|
|
2050
|
+
},
|
|
2051
|
+
this.publishableKey
|
|
2052
|
+
);
|
|
2053
|
+
return response.data;
|
|
2054
|
+
} catch (error) {
|
|
2055
|
+
lastError = error;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
throw lastError;
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Fail-fast recipient validation (e.g. Algorand asset opt-in). Fails open
|
|
2062
|
+
* on network errors — the backend still enforces at execution time — but a
|
|
2063
|
+
* definitive negative result is fatal.
|
|
2064
|
+
*
|
|
2065
|
+
* Deliberately NOT IP/geo-aware: generating deposit addresses headless
|
|
2066
|
+
* carries no region gate. Hosts that want the modal's geo behavior render
|
|
2067
|
+
* against the opt-in `useAllowedCountry` hook instead.
|
|
2068
|
+
*/
|
|
2069
|
+
async runStartChecks() {
|
|
2070
|
+
const recipientValid = await verifyRecipientAddress(
|
|
2071
|
+
{
|
|
2072
|
+
chain_type: this.destination.chainType,
|
|
2073
|
+
chain_id: this.destination.chainId,
|
|
2074
|
+
token_address: this.destination.tokenAddress,
|
|
2075
|
+
recipient_address: this.destination.recipientAddress
|
|
2076
|
+
},
|
|
2077
|
+
this.publishableKey
|
|
2078
|
+
).then((result) => result.valid).catch(() => null);
|
|
2079
|
+
if (recipientValid === false) {
|
|
2080
|
+
throw new SessionCheckError(
|
|
2081
|
+
"INVALID_RECIPIENT",
|
|
2082
|
+
"Recipient address cannot receive funds for this destination"
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
// -- Detection polling (port of useDepositPolling Effect 2) ----------------
|
|
2087
|
+
startDetectionLoop(token) {
|
|
2088
|
+
const poll = () => void this.pollExecutions(token);
|
|
2089
|
+
poll();
|
|
2090
|
+
this.detectionTimer = setInterval(poll, DETECTION_POLL_INTERVAL_MS);
|
|
2091
|
+
}
|
|
2092
|
+
async pollExecutions(token) {
|
|
2093
|
+
try {
|
|
2094
|
+
const response = await queryExecutions(
|
|
2095
|
+
this.externalUserId,
|
|
2096
|
+
this.publishableKey,
|
|
2097
|
+
"deposit" /* Deposit */
|
|
2098
|
+
);
|
|
2099
|
+
if (token !== this.runToken) return;
|
|
2100
|
+
if (this.pollErrorLatched) {
|
|
2101
|
+
this.pollErrorLatched = false;
|
|
2102
|
+
if (this.error && !this.error.fatal) {
|
|
2103
|
+
this.error = null;
|
|
2104
|
+
this.commit();
|
|
2105
|
+
this.notify();
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
const cutoffMs = this.baselineMs - LOOKBACK_MS;
|
|
2109
|
+
const sorted = [...response.data].sort((a, b) => {
|
|
2110
|
+
const timeA = a.created_at ? new Date(a.created_at).getTime() : 0;
|
|
2111
|
+
const timeB = b.created_at ? new Date(b.created_at).getTime() : 0;
|
|
2112
|
+
return timeB - timeA;
|
|
2113
|
+
});
|
|
2114
|
+
let candidate = null;
|
|
2115
|
+
for (const execution of sorted) {
|
|
2116
|
+
const createdMs = execution.created_at ? new Date(execution.created_at).getTime() : NaN;
|
|
2117
|
+
if (!Number.isFinite(createdMs) || createdMs < cutoffMs) continue;
|
|
2118
|
+
const trackedStatus = this.tracked.get(execution.id);
|
|
2119
|
+
const isTerminal = execution.status === "succeeded" /* SUCCEEDED */ || FAILURE_STATUSES.includes(execution.status);
|
|
2120
|
+
if (trackedStatus === void 0 && createdMs < this.baselineMs && isTerminal) {
|
|
2121
|
+
continue;
|
|
2122
|
+
}
|
|
2123
|
+
if (trackedStatus === void 0 || trackedStatus !== execution.status) {
|
|
2124
|
+
candidate = execution;
|
|
2125
|
+
break;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
if (!candidate) return;
|
|
2129
|
+
this.processExecutionChange(candidate);
|
|
2130
|
+
} catch (error) {
|
|
2131
|
+
if (token !== this.runToken) return;
|
|
2132
|
+
console.error("[unifold] failed to fetch executions:", error);
|
|
2133
|
+
if (!this.pollErrorLatched) {
|
|
2134
|
+
this.pollErrorLatched = true;
|
|
2135
|
+
this.error = {
|
|
2136
|
+
code: "POLLING_ERROR",
|
|
2137
|
+
message: "Failed to fetch deposit status",
|
|
2138
|
+
fatal: false,
|
|
2139
|
+
cause: error
|
|
2140
|
+
};
|
|
2141
|
+
this.commit();
|
|
2142
|
+
this.emitSessionEvent("deposit_session.errored" /* SESSION_ERRORED */, {
|
|
2143
|
+
sessionId: this.id,
|
|
2144
|
+
code: "POLLING_ERROR",
|
|
2145
|
+
message: "Failed to fetch deposit status",
|
|
2146
|
+
fatal: false
|
|
2147
|
+
});
|
|
2148
|
+
this.notify();
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
processExecutionChange(wire) {
|
|
2153
|
+
const previousStatus = this.tracked.get(wire.id) ?? null;
|
|
2154
|
+
this.tracked.set(wire.id, wire.status);
|
|
2155
|
+
const execution = mapDirectExecution(wire);
|
|
2156
|
+
const existingIndex = this.executions.findIndex((e) => e.id === execution.id);
|
|
2157
|
+
if (existingIndex >= 0) {
|
|
2158
|
+
this.executions = this.executions.map(
|
|
2159
|
+
(e, i) => i === existingIndex ? execution : e
|
|
2160
|
+
);
|
|
2161
|
+
} else {
|
|
2162
|
+
this.executions = [...this.executions, execution].sort((a, b) => {
|
|
2163
|
+
const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
|
|
2164
|
+
const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
|
2165
|
+
return timeB - timeA;
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2168
|
+
if (wire.status === "succeeded" /* SUCCEEDED */) {
|
|
2169
|
+
this.setStatus("succeeded");
|
|
2170
|
+
} else if (FAILURE_STATUSES.includes(wire.status)) {
|
|
2171
|
+
this.setStatus("failed");
|
|
2172
|
+
} else if (IN_PROGRESS_STATUSES.includes(wire.status)) {
|
|
2173
|
+
this.setStatus("processing");
|
|
2174
|
+
}
|
|
2175
|
+
this.commit();
|
|
2176
|
+
const eventCreated = this.executionEventTimestamp(wire);
|
|
2177
|
+
if (previousStatus === null) {
|
|
2178
|
+
this.emitExecutionEvent(
|
|
2179
|
+
"direct_execution.detected" /* EXECUTION_DETECTED */,
|
|
2180
|
+
execution,
|
|
2181
|
+
eventCreated
|
|
2182
|
+
);
|
|
2183
|
+
} else {
|
|
2184
|
+
this.emitExecutionEvent(
|
|
2185
|
+
"direct_execution.updated" /* EXECUTION_UPDATED */,
|
|
2186
|
+
{ ...execution, previousStatus },
|
|
2187
|
+
eventCreated
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
const wasInProgressOrNew = previousStatus === null || IN_PROGRESS_STATUSES.includes(previousStatus);
|
|
2191
|
+
if (wire.status === "succeeded" /* SUCCEEDED */ && wasInProgressOrNew) {
|
|
2192
|
+
this.emitExecutionEvent(
|
|
2193
|
+
"direct_execution.succeeded" /* EXECUTION_SUCCEEDED */,
|
|
2194
|
+
execution,
|
|
2195
|
+
eventCreated
|
|
2196
|
+
);
|
|
2197
|
+
} else if (FAILURE_STATUSES.includes(wire.status) && (previousStatus === null || !FAILURE_STATUSES.includes(previousStatus))) {
|
|
2198
|
+
this.emitExecutionEvent(
|
|
2199
|
+
"direct_execution.failed" /* EXECUTION_FAILED */,
|
|
2200
|
+
execution,
|
|
2201
|
+
eventCreated
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
this.notify();
|
|
2205
|
+
}
|
|
2206
|
+
// -- Scan nudge (port of useDepositPolling Effects 1 + 3) ------------------
|
|
2207
|
+
armConfirmation(trigger) {
|
|
2208
|
+
if (this.checkingDeposit || this.destroyed) return;
|
|
2209
|
+
if (this.armTimer) {
|
|
2210
|
+
clearTimeout(this.armTimer);
|
|
2211
|
+
this.armTimer = null;
|
|
2212
|
+
}
|
|
2213
|
+
this.checkingDeposit = true;
|
|
2214
|
+
this.commit();
|
|
2215
|
+
this.emitSessionEvent("deposit_session.confirmation_started" /* CONFIRMATION_STARTED */, {
|
|
2216
|
+
sessionId: this.id,
|
|
2217
|
+
trigger
|
|
2218
|
+
});
|
|
2219
|
+
this.notify();
|
|
2220
|
+
const token = this.runToken;
|
|
2221
|
+
const nudge = () => {
|
|
2222
|
+
if (token !== this.runToken) return;
|
|
2223
|
+
void Promise.all(
|
|
2224
|
+
this.addressIds.map(
|
|
2225
|
+
(id) => pollDirectExecutions({ deposit_wallet_id: id }, this.publishableKey).catch(
|
|
2226
|
+
() => {
|
|
2227
|
+
}
|
|
2228
|
+
)
|
|
2229
|
+
)
|
|
2230
|
+
);
|
|
2231
|
+
};
|
|
2232
|
+
nudge();
|
|
2233
|
+
this.nudgeTimer = setInterval(nudge, SCAN_NUDGE_INTERVAL_MS);
|
|
2234
|
+
}
|
|
2235
|
+
// -- Internals --------------------------------------------------------------
|
|
2236
|
+
setStatus(status) {
|
|
2237
|
+
this.status = status;
|
|
2238
|
+
}
|
|
2239
|
+
clearTimers() {
|
|
2240
|
+
if (this.detectionTimer) {
|
|
2241
|
+
clearInterval(this.detectionTimer);
|
|
2242
|
+
this.detectionTimer = null;
|
|
2243
|
+
}
|
|
2244
|
+
if (this.nudgeTimer) {
|
|
2245
|
+
clearInterval(this.nudgeTimer);
|
|
2246
|
+
this.nudgeTimer = null;
|
|
2247
|
+
}
|
|
2248
|
+
if (this.armTimer) {
|
|
2249
|
+
clearTimeout(this.armTimer);
|
|
2250
|
+
this.armTimer = null;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
buildSnapshot() {
|
|
2254
|
+
return {
|
|
2255
|
+
status: this.status,
|
|
2256
|
+
addresses: this.addresses,
|
|
2257
|
+
executions: this.executions,
|
|
2258
|
+
latestExecution: this.executions[0] ?? null,
|
|
2259
|
+
isCheckingDeposit: this.checkingDeposit,
|
|
2260
|
+
error: this.error
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
/** Rebuild the snapshot so getSnapshot() reflects current state. */
|
|
2264
|
+
commit() {
|
|
2265
|
+
this.snapshot = this.buildSnapshot();
|
|
2266
|
+
}
|
|
2267
|
+
notify() {
|
|
2268
|
+
this.listeners.forEach((listener) => {
|
|
2269
|
+
try {
|
|
2270
|
+
listener();
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
console.error("[unifold] snapshot listener threw", error);
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
executionEventTimestamp(wire) {
|
|
2277
|
+
if (wire.updated_at) return Math.floor(new Date(wire.updated_at).getTime() / 1e3);
|
|
2278
|
+
if (wire.created_at) return Math.floor(new Date(wire.created_at).getTime() / 1e3);
|
|
2279
|
+
return Math.floor(Date.now() / 1e3);
|
|
2280
|
+
}
|
|
2281
|
+
emitSessionEvent(type, object) {
|
|
2282
|
+
this.emitter.emit(type, {
|
|
2283
|
+
id: generatePrefixedKSUID("sevt"),
|
|
2284
|
+
type,
|
|
2285
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2286
|
+
method: this.method,
|
|
2287
|
+
data: { object }
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
emitExecutionEvent(type, object, created) {
|
|
2291
|
+
this.emitter.emit(type, {
|
|
2292
|
+
id: generatePrefixedKSUID("sevt"),
|
|
2293
|
+
type,
|
|
2294
|
+
created,
|
|
2295
|
+
method: this.method,
|
|
2296
|
+
data: { object }
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2299
|
+
};
|
|
2300
|
+
|
|
2301
|
+
// src/lib/client.ts
|
|
2302
|
+
var UnifoldClient = class {
|
|
2303
|
+
constructor(options) {
|
|
2304
|
+
__publicField(this, "publishableKey");
|
|
2305
|
+
const { publishableKey } = options;
|
|
2306
|
+
if (!publishableKey || publishableKey.trim() === "") {
|
|
2307
|
+
throw new Error("Unifold: publishableKey is required");
|
|
2308
|
+
}
|
|
2309
|
+
if (!publishableKey.startsWith("pk_test_") && !publishableKey.startsWith("pk_live_")) {
|
|
2310
|
+
console.warn(
|
|
2311
|
+
'Unifold: publishableKey should start with "pk_test_" or "pk_live_".'
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
this.publishableKey = publishableKey;
|
|
2315
|
+
}
|
|
2316
|
+
/** Create a headless deposit-session flow controller. */
|
|
2317
|
+
createDepositSession(params) {
|
|
2318
|
+
return new DepositSession({ ...params, publishableKey: this.publishableKey });
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Create (idempotently) and return the user's deposit addresses for a
|
|
2322
|
+
* destination — `POST /v1/public/deposit_addresses`.
|
|
2323
|
+
*/
|
|
2324
|
+
async getDepositAddresses(params) {
|
|
2325
|
+
const response = await createDepositAddress(
|
|
2326
|
+
{
|
|
2327
|
+
external_user_id: params.externalUserId,
|
|
2328
|
+
destination_chain_type: params.destination.chainType,
|
|
2329
|
+
destination_chain_id: params.destination.chainId,
|
|
2330
|
+
destination_token_address: params.destination.tokenAddress,
|
|
2331
|
+
recipient_address: params.destination.recipientAddress,
|
|
2332
|
+
contract_calls: params.destination.contractCalls
|
|
2333
|
+
},
|
|
2334
|
+
this.publishableKey
|
|
2335
|
+
);
|
|
2336
|
+
return response.data.map(mapWalletToDepositAddress);
|
|
2337
|
+
}
|
|
2338
|
+
/** List the user's executions — `POST /v1/public/direct_executions/query`. */
|
|
2339
|
+
async listExecutions(params) {
|
|
2340
|
+
const response = await queryExecutions(
|
|
2341
|
+
params.externalUserId,
|
|
2342
|
+
this.publishableKey,
|
|
2343
|
+
params.actionType ?? "deposit" /* Deposit */
|
|
2344
|
+
);
|
|
2345
|
+
return response.data.map(mapDirectExecution);
|
|
2346
|
+
}
|
|
2347
|
+
/** Source tokens/chains a user can deposit from for a destination. */
|
|
2348
|
+
async getSupportedDepositTokens(params) {
|
|
2349
|
+
const response = await getSupportedDepositTokens(
|
|
2350
|
+
this.publishableKey,
|
|
2351
|
+
params?.destination || params?.productType ? {
|
|
2352
|
+
...params.destination ? {
|
|
2353
|
+
destination_chain_type: params.destination.chainType,
|
|
2354
|
+
destination_chain_id: params.destination.chainId,
|
|
2355
|
+
destination_token_address: params.destination.tokenAddress
|
|
2356
|
+
} : {},
|
|
2357
|
+
...params.productType ? { product_type: params.productType } : {}
|
|
2358
|
+
} : void 0
|
|
2359
|
+
);
|
|
2360
|
+
return response.data;
|
|
2361
|
+
}
|
|
2362
|
+
/** Validate a recipient address for a destination (e.g. Algorand opt-in). */
|
|
2363
|
+
async verifyAddress(params) {
|
|
2364
|
+
const response = await verifyRecipientAddress(
|
|
2365
|
+
{
|
|
2366
|
+
chain_type: params.chainType,
|
|
2367
|
+
chain_id: params.chainId,
|
|
2368
|
+
token_address: params.tokenAddress,
|
|
2369
|
+
recipient_address: params.recipientAddress
|
|
2370
|
+
},
|
|
2371
|
+
this.publishableKey
|
|
2372
|
+
);
|
|
2373
|
+
return {
|
|
2374
|
+
valid: response.valid,
|
|
2375
|
+
failureCode: response.failure_code ?? null,
|
|
2376
|
+
metadata: response.metadata ?? null
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
/** Project-level configuration (feature flags, blocked countries, ...). */
|
|
2380
|
+
getProjectConfig(options) {
|
|
2381
|
+
return getProjectConfig(this.publishableKey, options);
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2384
|
+
function createUnifoldClient(options) {
|
|
2385
|
+
return new UnifoldClient(options);
|
|
2386
|
+
}
|
|
2387
|
+
|
|
1517
2388
|
// src/hooks/use-user-ip.ts
|
|
1518
2389
|
import { useQuery } from "@tanstack/react-query";
|
|
1519
2390
|
function useUserIp() {
|
|
@@ -1648,12 +2519,21 @@ var i18n = en_default;
|
|
|
1648
2519
|
export {
|
|
1649
2520
|
ActionType,
|
|
1650
2521
|
CheckoutEventType,
|
|
2522
|
+
DETECTION_ARM_DELAY_MS,
|
|
2523
|
+
DETECTION_POLL_INTERVAL_MS,
|
|
2524
|
+
DepositAddressValidationError,
|
|
1651
2525
|
DepositEventType,
|
|
2526
|
+
DepositSession,
|
|
2527
|
+
DepositSessionEventType,
|
|
2528
|
+
DepositSessionWaitError,
|
|
1652
2529
|
ExecutionStatus,
|
|
1653
2530
|
IneligibilityReason,
|
|
1654
2531
|
IntegrationProvider,
|
|
2532
|
+
LOOKBACK_MS,
|
|
2533
|
+
SCAN_NUDGE_INTERVAL_MS,
|
|
1655
2534
|
SOLANA_USDC_ADDRESS,
|
|
1656
2535
|
StripeApiResponseError,
|
|
2536
|
+
UnifoldClient,
|
|
1657
2537
|
WithdrawEventType,
|
|
1658
2538
|
authenticateIntegrationOAuth,
|
|
1659
2539
|
buildHypercoreTransaction,
|
|
@@ -1667,6 +2547,7 @@ export {
|
|
|
1667
2547
|
createIntegrationTransfer,
|
|
1668
2548
|
createOnrampSession,
|
|
1669
2549
|
createOnrampVerificationSession,
|
|
2550
|
+
createUnifoldClient,
|
|
1670
2551
|
exchangeOnrampVerificationToken,
|
|
1671
2552
|
formatStablecoinAmount,
|
|
1672
2553
|
generateKSUID,
|
|
@@ -1689,6 +2570,7 @@ export {
|
|
|
1689
2570
|
getExchanges,
|
|
1690
2571
|
getExternalWallets,
|
|
1691
2572
|
getFiatCurrencies,
|
|
2573
|
+
getFiatExchangeRates,
|
|
1692
2574
|
getIconUrl,
|
|
1693
2575
|
getIconUrlWithCdn,
|
|
1694
2576
|
getIntegrationExchanges,
|
|
@@ -1700,6 +2582,7 @@ export {
|
|
|
1700
2582
|
getOnrampVerificationSession,
|
|
1701
2583
|
getPreferredIconUrl,
|
|
1702
2584
|
getProjectConfig,
|
|
2585
|
+
getPublicIncident,
|
|
1703
2586
|
getSupportedDepositTokens,
|
|
1704
2587
|
getSupportedDestinationTokens,
|
|
1705
2588
|
getTokenChains,
|
|
@@ -1708,7 +2591,10 @@ export {
|
|
|
1708
2591
|
getWalletMobileDeepLink,
|
|
1709
2592
|
i18n,
|
|
1710
2593
|
isApplePayLimitReached,
|
|
2594
|
+
isDepositAddressValidationError,
|
|
1711
2595
|
listPaymentIntentExecutions,
|
|
2596
|
+
mapDirectExecution,
|
|
2597
|
+
mapWalletToDepositAddress,
|
|
1712
2598
|
pollDirectExecutions,
|
|
1713
2599
|
queryExecutions,
|
|
1714
2600
|
refreshIntegrationToken,
|