@pollar/react 0.11.5 → 0.12.0-rc.2
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 +1 -1
- package/dist/index.css +203 -11
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +165 -10
- package/dist/index.d.ts +165 -10
- package/dist/index.js +1390 -165
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1386 -167
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1038,7 +1038,7 @@ var PollarModalFooter = () => {
|
|
|
1038
1038
|
/* @__PURE__ */ jsxRuntime.jsx("img", { src: LOGO_POLLAR_WORDMARK_ON_DARK, alt: "Pollar", className: "pollar-footer-logo pollar-logo-on-dark" }),
|
|
1039
1039
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-footer-version", children: [
|
|
1040
1040
|
"v",
|
|
1041
|
-
"0.
|
|
1041
|
+
"0.12.0-rc.2"
|
|
1042
1042
|
] })
|
|
1043
1043
|
] });
|
|
1044
1044
|
};
|
|
@@ -1915,7 +1915,7 @@ function KycStatus({ status, label, className }) {
|
|
|
1915
1915
|
|
|
1916
1916
|
// src/components/kyc-modal/kyc-messages.ts
|
|
1917
1917
|
function kycErrorMessage(error, phase) {
|
|
1918
|
-
const code = error
|
|
1918
|
+
const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
1919
1919
|
switch (code) {
|
|
1920
1920
|
case "SDK_KYC_NOT_CONFIGURED":
|
|
1921
1921
|
return "Identity verification is not available for this app yet.";
|
|
@@ -2160,7 +2160,13 @@ function KycModal({
|
|
|
2160
2160
|
finish("approved");
|
|
2161
2161
|
return;
|
|
2162
2162
|
}
|
|
2163
|
-
|
|
2163
|
+
const { sessionId } = result;
|
|
2164
|
+
if (sessionId === void 0) throw new Error("The KYC provider answered without a session.");
|
|
2165
|
+
setSession({
|
|
2166
|
+
sessionId,
|
|
2167
|
+
...result.kycUrl !== void 0 ? { kycUrl: result.kycUrl } : {},
|
|
2168
|
+
...result.fields !== void 0 ? { fields: result.fields } : {}
|
|
2169
|
+
});
|
|
2164
2170
|
setStep("verifying");
|
|
2165
2171
|
} catch (e) {
|
|
2166
2172
|
const code = core.isPollarApiError(e) ? e.code : void 0;
|
|
@@ -2347,10 +2353,14 @@ function fieldErrorMessage(code, language) {
|
|
|
2347
2353
|
}
|
|
2348
2354
|
function fieldErrorsOf(error) {
|
|
2349
2355
|
if (!error || typeof error !== "object") return null;
|
|
2350
|
-
|
|
2351
|
-
|
|
2356
|
+
if (!("code" in error) || error.code !== "KYC_FORM_INVALID_ANSWERS") return null;
|
|
2357
|
+
const body = "body" in error ? error.body : void 0;
|
|
2358
|
+
const list = typeof body === "object" && body !== null && "errors" in body ? body.errors : void 0;
|
|
2359
|
+
if (!Array.isArray(list)) return null;
|
|
2360
|
+
const items = list;
|
|
2352
2361
|
const errors = {};
|
|
2353
|
-
for (const item of
|
|
2362
|
+
for (const item of items) {
|
|
2363
|
+
if (typeof item !== "object" || item === null || !("key" in item) || !("code" in item)) continue;
|
|
2354
2364
|
if (typeof item.key === "string" && typeof item.code === "string") errors[item.key] = item.code;
|
|
2355
2365
|
}
|
|
2356
2366
|
return Object.keys(errors).length ? errors : null;
|
|
@@ -3400,13 +3410,200 @@ function LoginModal({ onClose }) {
|
|
|
3400
3410
|
) });
|
|
3401
3411
|
}
|
|
3402
3412
|
|
|
3413
|
+
// src/lib/qr-code/index.tsx
|
|
3414
|
+
var import_ErrorCorrectLevel = __toESM(require_ErrorCorrectLevel());
|
|
3415
|
+
var import_QRCode = __toESM(require_QRCode());
|
|
3416
|
+
var QRCodeSvg = react.forwardRef(function QRCodeSvg2({ bgColor, bgD, fgD, fgColor, size, title, viewBoxSize, xmlns = "http://www.w3.org/2000/svg", ...props }, ref) {
|
|
3417
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...props, height: size, ref, viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`, width: size, xmlns, children: [
|
|
3418
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx("title", { children: title }) : null,
|
|
3419
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: bgD, fill: bgColor }),
|
|
3420
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: fgD, fill: fgColor })
|
|
3421
|
+
] });
|
|
3422
|
+
});
|
|
3423
|
+
QRCodeSvg.displayName = "QRCodeSvg";
|
|
3424
|
+
function bytesToBinaryString(bytes) {
|
|
3425
|
+
return bytes.map((b) => String.fromCharCode(b & 255)).join("");
|
|
3426
|
+
}
|
|
3427
|
+
function encodeStringToUtf8Bytes(input) {
|
|
3428
|
+
return Array.from(new TextEncoder().encode(input));
|
|
3429
|
+
}
|
|
3430
|
+
var QRCode = react.forwardRef(function QRCode2({ bgColor = "#FFFFFF", fgColor = "#000000", level = "L", size = 256, value, ...props }, ref) {
|
|
3431
|
+
const qrcode = new import_QRCode.default(-1, import_ErrorCorrectLevel.default[level]);
|
|
3432
|
+
const utf8Bytes = encodeStringToUtf8Bytes(value);
|
|
3433
|
+
const binaryString = bytesToBinaryString(utf8Bytes);
|
|
3434
|
+
qrcode.addData(binaryString, "Byte");
|
|
3435
|
+
qrcode.make();
|
|
3436
|
+
const cells = qrcode.modules;
|
|
3437
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3438
|
+
QRCodeSvg,
|
|
3439
|
+
{
|
|
3440
|
+
...props,
|
|
3441
|
+
bgColor,
|
|
3442
|
+
bgD: cells.map(
|
|
3443
|
+
(row, rowIndex) => row.map((cell, cellIndex) => !cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
|
|
3444
|
+
).join(" "),
|
|
3445
|
+
fgColor,
|
|
3446
|
+
fgD: cells.map(
|
|
3447
|
+
(row, rowIndex) => row.map((cell, cellIndex) => cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
|
|
3448
|
+
).join(" "),
|
|
3449
|
+
ref,
|
|
3450
|
+
size,
|
|
3451
|
+
viewBoxSize: cells.length
|
|
3452
|
+
}
|
|
3453
|
+
);
|
|
3454
|
+
});
|
|
3455
|
+
function RampWorkflow({
|
|
3456
|
+
client,
|
|
3457
|
+
snapshot,
|
|
3458
|
+
onChange,
|
|
3459
|
+
onBusyChange
|
|
3460
|
+
}) {
|
|
3461
|
+
const model = core.describeRampAction(snapshot);
|
|
3462
|
+
const locked = react.useRef(false);
|
|
3463
|
+
react.useEffect(() => {
|
|
3464
|
+
setFields({});
|
|
3465
|
+
setError(null);
|
|
3466
|
+
}, [model.action?.actionId]);
|
|
3467
|
+
const [busy, setBusy] = react.useState(false);
|
|
3468
|
+
const [error, setError] = react.useState(null);
|
|
3469
|
+
const [fields, setFields] = react.useState({});
|
|
3470
|
+
const fieldsComplete = model.action?.kind !== "collect_information" || model.action.fields.every((field) => {
|
|
3471
|
+
const value = (fields[field.key] ?? "").trim();
|
|
3472
|
+
if (!value) return field.optional;
|
|
3473
|
+
if (field.type === "email") return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
3474
|
+
return field.type !== "select" || field.options?.some((option) => option.value === value);
|
|
3475
|
+
});
|
|
3476
|
+
async function continueAction() {
|
|
3477
|
+
if (!model.action || locked.current || !fieldsComplete) return;
|
|
3478
|
+
locked.current = true;
|
|
3479
|
+
setBusy(true);
|
|
3480
|
+
onBusyChange?.(true);
|
|
3481
|
+
setError(null);
|
|
3482
|
+
try {
|
|
3483
|
+
onChange(
|
|
3484
|
+
model.canSign ? await client.signRampAction(snapshot.txId, snapshot) : await client.continueRamp(snapshot.txId, {
|
|
3485
|
+
actionId: model.action.actionId,
|
|
3486
|
+
transactionVersion: snapshot.transactionVersion,
|
|
3487
|
+
...model.action.kind === "collect_information" ? { fields } : {}
|
|
3488
|
+
})
|
|
3489
|
+
);
|
|
3490
|
+
} catch (e) {
|
|
3491
|
+
setError(e instanceof Error ? e.message : "Unable to continue.");
|
|
3492
|
+
} finally {
|
|
3493
|
+
locked.current = false;
|
|
3494
|
+
setBusy(false);
|
|
3495
|
+
onBusyChange?.(false);
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3499
|
+
"section",
|
|
3500
|
+
{
|
|
3501
|
+
className: "pollar-ramp-payment pollar-ramp-workflow",
|
|
3502
|
+
"aria-label": "Ramp progress",
|
|
3503
|
+
"aria-live": "polite",
|
|
3504
|
+
"aria-busy": busy,
|
|
3505
|
+
children: [
|
|
3506
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "pollar-ramp-payment-title", children: model.title }),
|
|
3507
|
+
snapshot.terms && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-ramp-terms", children: [
|
|
3508
|
+
snapshot.terms.fiatAmount,
|
|
3509
|
+
" ",
|
|
3510
|
+
snapshot.terms.fiatCurrency,
|
|
3511
|
+
" \xB7 ",
|
|
3512
|
+
snapshot.terms.cryptoAmount,
|
|
3513
|
+
" ",
|
|
3514
|
+
snapshot.terms.assetCode,
|
|
3515
|
+
/* @__PURE__ */ jsxRuntime.jsx("br", {}),
|
|
3516
|
+
"Includes ",
|
|
3517
|
+
snapshot.terms.feeAmount,
|
|
3518
|
+
" ",
|
|
3519
|
+
snapshot.terms.feeCurrency,
|
|
3520
|
+
" fee"
|
|
3521
|
+
] }),
|
|
3522
|
+
model.details.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-ramp-workflow-detail", children: [
|
|
3523
|
+
/* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
|
|
3524
|
+
item.label,
|
|
3525
|
+
": "
|
|
3526
|
+
] }),
|
|
3527
|
+
item.value
|
|
3528
|
+
] }, index)),
|
|
3529
|
+
model.links.map((link, index) => /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("a", { className: "pollar-btn-primary pollar-ramp-hosted-link", href: link.url, target: "_blank", rel: "noopener noreferrer", children: link.label }) }, index)),
|
|
3530
|
+
model.qr && /* @__PURE__ */ jsxRuntime.jsx(QRCode, { value: model.qr, size: 180 }),
|
|
3531
|
+
model.qr && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3532
|
+
"button",
|
|
3533
|
+
{
|
|
3534
|
+
className: "pollar-btn-secondary",
|
|
3535
|
+
type: "button",
|
|
3536
|
+
onClick: () => void navigator.clipboard.writeText(model.qr).catch(() => setError("Unable to copy. Select the payment code to copy it manually.")),
|
|
3537
|
+
children: "Copy payment code"
|
|
3538
|
+
}
|
|
3539
|
+
),
|
|
3540
|
+
model.action?.kind === "collect_information" && model.action.fields.map((field) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-ramp-field", children: [
|
|
3541
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-ramp-label", children: [
|
|
3542
|
+
field.label,
|
|
3543
|
+
field.optional ? " (optional)" : ""
|
|
3544
|
+
] }),
|
|
3545
|
+
field.type === "select" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3546
|
+
"select",
|
|
3547
|
+
{
|
|
3548
|
+
className: "pollar-ramp-input",
|
|
3549
|
+
disabled: busy,
|
|
3550
|
+
value: fields[field.key] ?? "",
|
|
3551
|
+
onChange: (e) => setFields((previous) => ({ ...previous, [field.key]: e.target.value })),
|
|
3552
|
+
children: [
|
|
3553
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Choose" }),
|
|
3554
|
+
field.options?.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, children: option.label }, option.value))
|
|
3555
|
+
]
|
|
3556
|
+
}
|
|
3557
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
3558
|
+
"input",
|
|
3559
|
+
{
|
|
3560
|
+
className: "pollar-ramp-input",
|
|
3561
|
+
disabled: busy,
|
|
3562
|
+
type: field.type,
|
|
3563
|
+
placeholder: field.placeholder,
|
|
3564
|
+
value: fields[field.key] ?? "",
|
|
3565
|
+
onChange: (e) => setFields((previous) => ({ ...previous, [field.key]: e.target.value }))
|
|
3566
|
+
}
|
|
3567
|
+
),
|
|
3568
|
+
field.hint && /* @__PURE__ */ jsxRuntime.jsx("small", { className: "pollar-ramp-field-hint", children: field.hint })
|
|
3569
|
+
] }, field.key)),
|
|
3570
|
+
(model.canContinue || model.canSign) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3571
|
+
"button",
|
|
3572
|
+
{
|
|
3573
|
+
className: "pollar-btn-primary",
|
|
3574
|
+
type: "button",
|
|
3575
|
+
disabled: busy || !fieldsComplete,
|
|
3576
|
+
onClick: () => void continueAction(),
|
|
3577
|
+
children: busy ? model.canSign ? "Waiting for your wallet\u2026" : "Continuing\u2026" : model.canSign ? "Authorize" : "Continue"
|
|
3578
|
+
}
|
|
3579
|
+
),
|
|
3580
|
+
snapshot.milestones?.map((milestone, index) => /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
3581
|
+
milestone.kind.replace(/_/g, " "),
|
|
3582
|
+
" \xB7 ",
|
|
3583
|
+
milestone.verifiedAt
|
|
3584
|
+
] }, index)),
|
|
3585
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-field-error", role: "alert", children: error }),
|
|
3586
|
+
/* @__PURE__ */ jsxRuntime.jsxs("small", { className: "pollar-ramp-reference", children: [
|
|
3587
|
+
"Reference: ",
|
|
3588
|
+
snapshot.txId
|
|
3589
|
+
] })
|
|
3590
|
+
]
|
|
3591
|
+
}
|
|
3592
|
+
);
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3403
3595
|
// src/components/ramp-widget/ramp-kyc.ts
|
|
3404
3596
|
var nonEmpty = (value) => typeof value === "string" && !!value.trim();
|
|
3405
3597
|
function requiredRampKyc(error) {
|
|
3406
3598
|
if (!error || typeof error !== "object") return null;
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3599
|
+
if (!("code" in error) || error.code !== "SDK_RAMPS_KYC_REQUIRED") return null;
|
|
3600
|
+
const body = "body" in error ? error.body : void 0;
|
|
3601
|
+
if (typeof body !== "object" || body === null) return null;
|
|
3602
|
+
const rampProviderId = "rampProviderId" in body ? body.rampProviderId : void 0;
|
|
3603
|
+
const corridorId = "corridorId" in body ? body.corridorId : void 0;
|
|
3604
|
+
const requirementType = "requirementType" in body ? body.requirementType : void 0;
|
|
3605
|
+
const optionId = "optionId" in body ? body.optionId : void 0;
|
|
3606
|
+
const kycProviderId = "kycProviderId" in body ? body.kycProviderId : void 0;
|
|
3410
3607
|
if (!nonEmpty(rampProviderId) || !nonEmpty(corridorId)) return null;
|
|
3411
3608
|
if ((requirementType === "FORM" || requirementType === "REGISTRY_CHECK" || requirementType === "PROVIDER_REGISTRATION") && nonEmpty(optionId)) {
|
|
3412
3609
|
return { rampProviderId, corridorId, type: requirementType, optionId };
|
|
@@ -3473,13 +3670,14 @@ function RouteDisplay({ quote, busy = false, disabled = false, onSelect }) {
|
|
|
3473
3670
|
children: [
|
|
3474
3671
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-route-left", children: [
|
|
3475
3672
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-provider", children: quote.provider }),
|
|
3476
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-meta", children: busy ? "Starting\u2026" :
|
|
3673
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-meta", children: busy ? "Starting\u2026" : [
|
|
3674
|
+
quote.route ? `${quote.route.asset.code} on ${quote.route.asset.chain}` : null,
|
|
3675
|
+
RAIL_LABELS[quote.rail] ?? quote.rail,
|
|
3676
|
+
quote.estimatedTime
|
|
3677
|
+
].filter(Boolean).join(" \xB7 ") })
|
|
3477
3678
|
] }),
|
|
3478
3679
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-route-right", children: busy ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3479
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3480
|
-
quote.fee,
|
|
3481
|
-
"% fee"
|
|
3482
|
-
] }),
|
|
3680
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-fee", children: quote.terms ? `${quote.terms.fiatAmount} ${quote.terms.fiatCurrency} \xB7 ${quote.terms.cryptoAmount} ${quote.terms.assetCode} \xB7 Includes ${quote.terms.feeAmount} ${quote.terms.feeCurrency}` : `${quote.fee}% fee` }),
|
|
3483
3681
|
quote.recommended && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-badge", children: "Best rate" })
|
|
3484
3682
|
] }) })
|
|
3485
3683
|
]
|
|
@@ -3559,6 +3757,8 @@ function displayValue(field) {
|
|
|
3559
3757
|
return Number.isNaN(at.getTime()) ? field.value : at.toLocaleString();
|
|
3560
3758
|
}
|
|
3561
3759
|
function RampWidgetTemplate({
|
|
3760
|
+
workflowContent,
|
|
3761
|
+
routeSelector,
|
|
3562
3762
|
theme,
|
|
3563
3763
|
accentColor,
|
|
3564
3764
|
styleOverrides,
|
|
@@ -3595,6 +3795,11 @@ function RampWidgetTemplate({
|
|
|
3595
3795
|
depositInstructions,
|
|
3596
3796
|
canComplete,
|
|
3597
3797
|
completing,
|
|
3798
|
+
savedRamps = [],
|
|
3799
|
+
onResumeRamp,
|
|
3800
|
+
onDismissRamp,
|
|
3801
|
+
onDismissCurrentRamp,
|
|
3802
|
+
onExploreRoutes,
|
|
3598
3803
|
errorMsg,
|
|
3599
3804
|
noticeMsg,
|
|
3600
3805
|
onDirectionChange,
|
|
@@ -3633,7 +3838,7 @@ function RampWidgetTemplate({
|
|
|
3633
3838
|
// money, not who you are. Say which, rather than asserting the Bridge case
|
|
3634
3839
|
// over every provider.
|
|
3635
3840
|
contact: `${provider || "This provider"} needs ${listOf(requiredFields.filter((f) => !f.optional).map((f) => f.label.toLowerCase()))} to continue`,
|
|
3636
|
-
status: `Finish the flow at ${provider || "the provider"} to continue`,
|
|
3841
|
+
status: workflowContent ? "Follow the next step below to continue" : `Finish the flow at ${provider || "the provider"} to continue`,
|
|
3637
3842
|
error: "Please try again"
|
|
3638
3843
|
};
|
|
3639
3844
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-card pollar-ramp-modal", style: cssVars, onClick: (e) => e.stopPropagation(), children: [
|
|
@@ -3687,7 +3892,39 @@ function RampWidgetTemplate({
|
|
|
3687
3892
|
}
|
|
3688
3893
|
)
|
|
3689
3894
|
] }),
|
|
3895
|
+
step === "input" && savedRamps.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-ramp-payment pollar-ramp-saved", "aria-label": "Pending operations", children: [
|
|
3896
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: "Your pending operations are saved. Starting another route does not cancel them." }),
|
|
3897
|
+
savedRamps.map((ramp) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-saved-row", children: [
|
|
3898
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3899
|
+
"button",
|
|
3900
|
+
{
|
|
3901
|
+
type: "button",
|
|
3902
|
+
className: "pollar-btn-secondary",
|
|
3903
|
+
disabled: isLoading,
|
|
3904
|
+
onClick: () => onResumeRamp?.(ramp.txId),
|
|
3905
|
+
children: [
|
|
3906
|
+
"Resume ",
|
|
3907
|
+
ramp.provider,
|
|
3908
|
+
" \xB7 ",
|
|
3909
|
+
ramp.txId.slice(-8)
|
|
3910
|
+
]
|
|
3911
|
+
}
|
|
3912
|
+
),
|
|
3913
|
+
onDismissRamp && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3914
|
+
"button",
|
|
3915
|
+
{
|
|
3916
|
+
type: "button",
|
|
3917
|
+
className: "pollar-btn-secondary",
|
|
3918
|
+
disabled: isLoading,
|
|
3919
|
+
"aria-label": `Remove ${ramp.provider} ${ramp.txId.slice(-8)} from pending list`,
|
|
3920
|
+
onClick: () => onDismissRamp(ramp.txId),
|
|
3921
|
+
children: "Remove"
|
|
3922
|
+
}
|
|
3923
|
+
)
|
|
3924
|
+
] }, ramp.txId))
|
|
3925
|
+
] }),
|
|
3690
3926
|
step === "input" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3927
|
+
noticeMsg && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", role: "status", children: noticeMsg }),
|
|
3691
3928
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-tabs", children: [
|
|
3692
3929
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3693
3930
|
"button",
|
|
@@ -3711,25 +3948,36 @@ function RampWidgetTemplate({
|
|
|
3711
3948
|
)
|
|
3712
3949
|
] }),
|
|
3713
3950
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-field", children: [
|
|
3714
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-ramp-label", children: "Country" }),
|
|
3951
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-ramp-label", htmlFor: "pollar-ramp-country", children: "Country" }),
|
|
3715
3952
|
countriesLoading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-input pollar-select-loading", children: [
|
|
3716
3953
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
|
|
3717
3954
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Loading countries\u2026" })
|
|
3718
|
-
] }) : countries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-error", children: "No ramp providers available on this network yet." }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3955
|
+
] }) : countries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-error", children: "No ramp providers available on this network yet." }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
3956
|
+
"select",
|
|
3957
|
+
{
|
|
3958
|
+
id: "pollar-ramp-country",
|
|
3959
|
+
className: "pollar-ramp-input",
|
|
3960
|
+
value: country,
|
|
3961
|
+
onChange: (e) => onCountryChange(e.target.value),
|
|
3962
|
+
children: countries.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: c.code, children: [
|
|
3963
|
+
flagEmoji(c.code),
|
|
3964
|
+
" ",
|
|
3965
|
+
c.code,
|
|
3966
|
+
c.currency ? ` \u2014 ${c.currency}` : ""
|
|
3967
|
+
] }, c.code))
|
|
3968
|
+
}
|
|
3969
|
+
)
|
|
3724
3970
|
] }),
|
|
3971
|
+
routeSelector,
|
|
3725
3972
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-field", children: [
|
|
3726
|
-
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-ramp-label", children: [
|
|
3973
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-ramp-label", htmlFor: "pollar-ramp-amount", children: [
|
|
3727
3974
|
"Amount",
|
|
3728
3975
|
currency ? ` (${currency})` : ""
|
|
3729
3976
|
] }),
|
|
3730
3977
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3731
3978
|
"input",
|
|
3732
3979
|
{
|
|
3980
|
+
id: "pollar-ramp-amount",
|
|
3733
3981
|
type: "number",
|
|
3734
3982
|
className: "pollar-ramp-input",
|
|
3735
3983
|
placeholder: "0.00",
|
|
@@ -3835,7 +4083,16 @@ function RampWidgetTemplate({
|
|
|
3835
4083
|
)
|
|
3836
4084
|
] })
|
|
3837
4085
|
] }),
|
|
3838
|
-
step === "status" &&
|
|
4086
|
+
step === "status" && workflowContent,
|
|
4087
|
+
step === "status" && onDismissCurrentRamp && txStatus !== "completed" && txStatus !== "failed" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-explore", children: [
|
|
4088
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", disabled: completing, onClick: onDismissCurrentRamp, children: "Remove from pending list" }),
|
|
4089
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: "This only hides the operation in this tab. To request cancellation, open the provider link above when available or contact the provider\u2019s support. Payments and refunds must be confirmed by the provider." })
|
|
4090
|
+
] }),
|
|
4091
|
+
step === "status" && onExploreRoutes && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-explore", children: [
|
|
4092
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", disabled: completing, onClick: onExploreRoutes, children: txStatus === "completed" || txStatus === "failed" ? "Start another operation" : "Try another route" }),
|
|
4093
|
+
txStatus !== "completed" && txStatus !== "failed" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: "Your current operation stays saved so you can resume it later." })
|
|
4094
|
+
] }),
|
|
4095
|
+
step === "status" && !workflowContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment", children: [
|
|
3839
4096
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
|
|
3840
4097
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: "Provider" }),
|
|
3841
4098
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: provider }) })
|
|
@@ -3967,7 +4224,7 @@ function RampWidgetTemplate({
|
|
|
3967
4224
|
canComplete && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-primary", disabled: completing, onClick: onCompleteWithdraw, children: completing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3968
4225
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
|
|
3969
4226
|
"Submitting\u2026"
|
|
3970
|
-
] }) : "
|
|
4227
|
+
] }) : "Check verification and continue" }),
|
|
3971
4228
|
errorMsg && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", style: { color: "var(--pollar-error-text)" }, children: errorMsg }),
|
|
3972
4229
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onClose, children: txStatus === "completed" ? "Done" : "Close" })
|
|
3973
4230
|
] }),
|
|
@@ -4133,19 +4390,26 @@ var SHARED_FIELD_LABELS = {
|
|
|
4133
4390
|
country_code: "Pa\xEDs de residencia"
|
|
4134
4391
|
}
|
|
4135
4392
|
};
|
|
4136
|
-
var errorCode = (error) =>
|
|
4393
|
+
var errorCode = (error) => typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
4394
|
+
var errorBody = (error) => {
|
|
4395
|
+
const body = typeof error === "object" && error !== null && "body" in error ? error.body : void 0;
|
|
4396
|
+
return typeof body === "object" && body !== null ? body : void 0;
|
|
4397
|
+
};
|
|
4137
4398
|
function missingFieldsOf(error) {
|
|
4138
|
-
const body = error
|
|
4139
|
-
|
|
4399
|
+
const body = errorBody(error);
|
|
4400
|
+
const missing = body && "missing" in body ? body.missing : void 0;
|
|
4401
|
+
return Array.isArray(missing) ? missing.filter((item) => typeof item === "string") : [];
|
|
4140
4402
|
}
|
|
4141
4403
|
function registrationFixOf(error) {
|
|
4142
|
-
const body = error
|
|
4404
|
+
const body = errorBody(error);
|
|
4405
|
+
const invalid = body && "invalid" in body ? body.invalid : void 0;
|
|
4406
|
+
const formList = body && "forms" in body ? body.forms : void 0;
|
|
4143
4407
|
const strings = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
4144
|
-
const forms = Array.isArray(
|
|
4145
|
-
|
|
4146
|
-
return
|
|
4408
|
+
const forms = Array.isArray(formList) ? formList.flatMap((item) => {
|
|
4409
|
+
if (typeof item !== "object" || item === null || !("formId" in item) || typeof item.formId !== "string") return [];
|
|
4410
|
+
return [{ formId: item.formId, keys: strings("keys" in item ? item.keys : void 0) }];
|
|
4147
4411
|
}) : [];
|
|
4148
|
-
return { invalid: strings(
|
|
4412
|
+
return { invalid: strings(invalid), forms };
|
|
4149
4413
|
}
|
|
4150
4414
|
function RegistryCheckModal({ optionId, progress, onClose, onApproved }) {
|
|
4151
4415
|
const { getClient, styles } = usePollar();
|
|
@@ -4465,9 +4729,51 @@ function ProviderRegistrationModal({
|
|
|
4465
4729
|
}
|
|
4466
4730
|
) });
|
|
4467
4731
|
}
|
|
4732
|
+
|
|
4733
|
+
// src/components/ramp-widget/ramp-quotes.ts
|
|
4734
|
+
function unavailableRampMessage(country, unavailable) {
|
|
4735
|
+
if (!unavailable.length) return `No ramp providers available for ${country} yet.`;
|
|
4736
|
+
return unavailable.map(
|
|
4737
|
+
({ provider, code }) => code === "SDK_RAMPS_PROVIDER_NOT_CONFIGURED" ? `${provider} is not configured correctly. Contact the app administrator to enable this provider.` : `${provider} is temporarily unavailable. Please try again later.`
|
|
4738
|
+
).join(" ");
|
|
4739
|
+
}
|
|
4740
|
+
async function quoteRampOptions(client, query, routes) {
|
|
4741
|
+
const matching = routes.filter(
|
|
4742
|
+
(route) => route.country === query.country && route.direction === query.direction && route.fiatCurrency === query.currency
|
|
4743
|
+
);
|
|
4744
|
+
const responses = await Promise.allSettled([
|
|
4745
|
+
client.getRampsQuote(query),
|
|
4746
|
+
...matching.map((route) => client.getRampsQuote({ ...query, routeId: route.routeId, chain: route.asset.chain }))
|
|
4747
|
+
]);
|
|
4748
|
+
const results = responses.flatMap((response) => response.status === "fulfilled" ? [response.value] : []);
|
|
4749
|
+
if (!results.length) throw responses[0].reason;
|
|
4750
|
+
const quotes = /* @__PURE__ */ new Map();
|
|
4751
|
+
const requirements = /* @__PURE__ */ new Map();
|
|
4752
|
+
const unavailable = /* @__PURE__ */ new Map();
|
|
4753
|
+
for (const result of results) {
|
|
4754
|
+
for (const quote of result.quotes) {
|
|
4755
|
+
const key = JSON.stringify([quote.provider, quote.route?.routeId, quote.route?.asset.chain, quote.rail, quote.protocol]);
|
|
4756
|
+
if (!quotes.has(key)) quotes.set(key, quote);
|
|
4757
|
+
}
|
|
4758
|
+
for (const requirement of result.requirementsRequired ?? [])
|
|
4759
|
+
requirements.set(`${requirement.rampProviderId}:${requirement.corridorId}`, requirement);
|
|
4760
|
+
for (const item of result.unavailable ?? []) unavailable.set(item.provider, item);
|
|
4761
|
+
}
|
|
4762
|
+
const failed = responses.find((response) => response.status === "rejected");
|
|
4763
|
+
if (!quotes.size && !requirements.size && failed?.status === "rejected") throw failed.reason;
|
|
4764
|
+
return {
|
|
4765
|
+
quotes: [...quotes.values()],
|
|
4766
|
+
requirementsRequired: [...requirements.values()],
|
|
4767
|
+
unavailable: [...unavailable.values()]
|
|
4768
|
+
};
|
|
4769
|
+
}
|
|
4468
4770
|
var TERMINAL = ["completed", "failed"];
|
|
4771
|
+
var BANK_TYPES = ["CLABE", "PIX", "PSE", "ACH", "BREB"];
|
|
4772
|
+
function isBankType(value) {
|
|
4773
|
+
return BANK_TYPES.some((type) => type === value);
|
|
4774
|
+
}
|
|
4469
4775
|
function requiredFieldsOf(quote) {
|
|
4470
|
-
return quote.requiredFields
|
|
4776
|
+
return "requiredFields" in quote ? quote.requiredFields : [];
|
|
4471
4777
|
}
|
|
4472
4778
|
function flowStepsOf(quotes, selected) {
|
|
4473
4779
|
const needsDetails = selected != null ? requiredFieldsOf(selected).length > 0 : quotes.some((q) => requiredFieldsOf(q).length > 0);
|
|
@@ -4481,7 +4787,8 @@ var STEP_LABEL = {
|
|
|
4481
4787
|
status: "Complete"
|
|
4482
4788
|
};
|
|
4483
4789
|
function brokenLimitOf(amount, quote) {
|
|
4484
|
-
const
|
|
4790
|
+
const minAmount = "minAmount" in quote ? quote.minAmount : void 0;
|
|
4791
|
+
const maxAmount = "maxAmount" in quote ? quote.maxAmount : void 0;
|
|
4485
4792
|
if (!Number.isFinite(amount)) return null;
|
|
4486
4793
|
if (minAmount != null && amount < minAmount) return { limit: "min", value: minAmount };
|
|
4487
4794
|
if (maxAmount != null && amount > maxAmount) return { limit: "max", value: maxAmount };
|
|
@@ -4505,17 +4812,49 @@ var RAMP_ERROR_MESSAGES = {
|
|
|
4505
4812
|
SDK_RAMPS_ONCHAIN_SUBMIT_FAILED: "The network rejected the transaction, so nothing was sent. The wallet may need XLM for the fee."
|
|
4506
4813
|
};
|
|
4507
4814
|
function rampErrorMessage(e, fallback) {
|
|
4508
|
-
const
|
|
4509
|
-
const code = e?.code;
|
|
4815
|
+
const code = typeof e === "object" && e !== null && "code" in e ? e.code : void 0;
|
|
4510
4816
|
if (typeof code !== "string") return e instanceof Error ? e.message : fallback;
|
|
4511
|
-
const
|
|
4512
|
-
const
|
|
4513
|
-
const
|
|
4817
|
+
const raw = typeof e === "object" && e !== null && "body" in e ? e.body : void 0;
|
|
4818
|
+
const body = typeof raw === "object" && raw !== null ? raw : {};
|
|
4819
|
+
const limit = "limit" in body ? body.limit : void 0;
|
|
4820
|
+
const value = "limitAmount" in body ? body.limitAmount : void 0;
|
|
4821
|
+
const currency = "limitCurrency" in body ? body.limitCurrency : void 0;
|
|
4514
4822
|
if ((limit === "min" || limit === "max") && typeof value === "number" && typeof currency === "string") {
|
|
4515
4823
|
return limitMessage(limit, value, currency);
|
|
4516
4824
|
}
|
|
4517
4825
|
return RAMP_ERROR_MESSAGES[code] ?? (e instanceof Error ? e.message : fallback);
|
|
4518
4826
|
}
|
|
4827
|
+
function readRampStorage(key) {
|
|
4828
|
+
try {
|
|
4829
|
+
return typeof window === "undefined" ? null : window.sessionStorage.getItem(key);
|
|
4830
|
+
} catch {
|
|
4831
|
+
return null;
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4834
|
+
function writeRampStorage(key, value) {
|
|
4835
|
+
try {
|
|
4836
|
+
if (typeof window === "undefined") return;
|
|
4837
|
+
if (value === null) window.sessionStorage.removeItem(key);
|
|
4838
|
+
else window.sessionStorage.setItem(key, value);
|
|
4839
|
+
} catch {
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
function savedRampsOf(key) {
|
|
4843
|
+
try {
|
|
4844
|
+
const value = JSON.parse(readRampStorage(`${key}:saved`) ?? "[]");
|
|
4845
|
+
return Array.isArray(value) ? value.filter((item) => typeof item?.txId === "string" && typeof item?.provider === "string") : [];
|
|
4846
|
+
} catch {
|
|
4847
|
+
return [];
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
function hiddenRampsOf(key) {
|
|
4851
|
+
try {
|
|
4852
|
+
const value = JSON.parse(readRampStorage(`${key}:hidden`) ?? "[]");
|
|
4853
|
+
return Array.isArray(value) ? value.filter((id) => typeof id === "string") : [];
|
|
4854
|
+
} catch {
|
|
4855
|
+
return [];
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4519
4858
|
function explorerTxUrl2(chain, hash, network) {
|
|
4520
4859
|
if (!hash) return null;
|
|
4521
4860
|
const testnet = network === "testnet";
|
|
@@ -4524,7 +4863,7 @@ function explorerTxUrl2(chain, hash, network) {
|
|
|
4524
4863
|
return `https://stellar.expert/explorer/${testnet ? "testnet" : "public"}/tx/${hash}`;
|
|
4525
4864
|
}
|
|
4526
4865
|
function RampWidget({ onClose }) {
|
|
4527
|
-
const { getClient,
|
|
4866
|
+
const { getClient, wallet, styles, network } = usePollar();
|
|
4528
4867
|
const walletAddress = wallet?.address ?? "";
|
|
4529
4868
|
const client = getClient();
|
|
4530
4869
|
const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
|
|
@@ -4535,7 +4874,7 @@ function RampWidget({ onClose }) {
|
|
|
4535
4874
|
const [country, setCountry] = react.useState("");
|
|
4536
4875
|
const [fieldValues, setFieldValues] = react.useState({});
|
|
4537
4876
|
const setFieldValue = (key, value) => setFieldValues((v) => ({ ...v, [key]: value }));
|
|
4538
|
-
const [
|
|
4877
|
+
const [legacyCountries, setCountries] = react.useState([]);
|
|
4539
4878
|
const [countriesLoading, setCountriesLoading] = react.useState(true);
|
|
4540
4879
|
const [refreshing, setRefreshing] = react.useState(false);
|
|
4541
4880
|
const [quotes, setQuotes] = react.useState([]);
|
|
@@ -4565,9 +4904,88 @@ function RampWidget({ onClose }) {
|
|
|
4565
4904
|
const [fundsInTransit, setFundsInTransit] = react.useState(false);
|
|
4566
4905
|
const [retrying, setRetrying] = react.useState(false);
|
|
4567
4906
|
const [depositInstructions, setDepositInstructions] = react.useState(null);
|
|
4907
|
+
const [workflow, setWorkflow] = react.useState(null);
|
|
4908
|
+
const workflowRef = react.useRef(null);
|
|
4909
|
+
const storageKey = `pollar:ramp:${client.apiKey}:${walletAddress}`;
|
|
4910
|
+
const [savedRamps, setSavedRamps] = react.useState([]);
|
|
4911
|
+
const [legacySignature, setLegacySignature] = react.useState(void 0);
|
|
4912
|
+
function acceptWorkflow(incoming) {
|
|
4913
|
+
if (hiddenRampsOf(storageKey).includes(incoming.txId)) return false;
|
|
4914
|
+
const next = core.mergeRampSnapshot(workflowRef.current, incoming);
|
|
4915
|
+
workflowRef.current = next;
|
|
4916
|
+
setWorkflow(next);
|
|
4917
|
+
if (next === incoming) {
|
|
4918
|
+
const pending = savedRampsOf(storageKey).filter((item) => item.txId !== incoming.txId);
|
|
4919
|
+
if (!["completed", "failed", "refunded"].includes(incoming.lifecycleState ?? incoming.status))
|
|
4920
|
+
pending.push({
|
|
4921
|
+
txId: incoming.txId,
|
|
4922
|
+
provider: "provider" in incoming && typeof incoming.provider === "string" ? incoming.provider : provider || "Ramp"
|
|
4923
|
+
});
|
|
4924
|
+
writeRampStorage(`${storageKey}:saved`, JSON.stringify(pending));
|
|
4925
|
+
writeRampStorage(storageKey, incoming.txId);
|
|
4926
|
+
writeRampStorage(`${storageKey}:selection`, null);
|
|
4927
|
+
setSavedRamps(pending);
|
|
4928
|
+
}
|
|
4929
|
+
return next === incoming;
|
|
4930
|
+
}
|
|
4931
|
+
const [routes, setRoutes] = react.useState([]);
|
|
4932
|
+
const countries = core.mergeRampCountries(legacyCountries, routes);
|
|
4933
|
+
react.useEffect(() => {
|
|
4934
|
+
if (!country && countries[0]) {
|
|
4935
|
+
setCountry(countries[0].code);
|
|
4936
|
+
setCurrency(countries[0].currency ?? "");
|
|
4937
|
+
}
|
|
4938
|
+
}, [countries, country]);
|
|
4939
|
+
react.useEffect(() => {
|
|
4940
|
+
let active = true;
|
|
4941
|
+
void client.getRampRoutes().then((result) => {
|
|
4942
|
+
if (active) setRoutes(result.routes);
|
|
4943
|
+
}).catch(() => {
|
|
4944
|
+
});
|
|
4945
|
+
return () => {
|
|
4946
|
+
active = false;
|
|
4947
|
+
};
|
|
4948
|
+
}, [client]);
|
|
4568
4949
|
const [completing, setCompleting] = react.useState(false);
|
|
4950
|
+
const completionLocked = react.useRef(false);
|
|
4569
4951
|
const [errorMsg, setErrorMsg] = react.useState(null);
|
|
4570
|
-
|
|
4952
|
+
react.useEffect(() => {
|
|
4953
|
+
if (typeof window === "undefined" || !walletAddress) return;
|
|
4954
|
+
setSavedRamps(savedRampsOf(storageKey));
|
|
4955
|
+
if (readRampStorage(`${storageKey}:selection`) === "1") return;
|
|
4956
|
+
const hidden = hiddenRampsOf(storageKey);
|
|
4957
|
+
const savedId = [new URLSearchParams(window.location.search).get("rampTxId"), readRampStorage(storageKey)].find(
|
|
4958
|
+
(id) => id && !hidden.includes(id)
|
|
4959
|
+
);
|
|
4960
|
+
if (!savedId) return;
|
|
4961
|
+
let active = true;
|
|
4962
|
+
void client.getRampTransaction(savedId).then(async (result) => {
|
|
4963
|
+
if (!active || result.transactionVersion === void 0) return;
|
|
4964
|
+
setDirection(result.direction);
|
|
4965
|
+
await applyResult(result);
|
|
4966
|
+
}).catch(() => {
|
|
4967
|
+
if (active)
|
|
4968
|
+
setErrorMsg("Could not restore the saved ramp. Retry opening the modal; do not create a replacement order.");
|
|
4969
|
+
});
|
|
4970
|
+
return () => {
|
|
4971
|
+
active = false;
|
|
4972
|
+
};
|
|
4973
|
+
}, [client, storageKey, walletAddress]);
|
|
4974
|
+
async function resumeSavedRamp(id) {
|
|
4975
|
+
if (completionLocked.current) return;
|
|
4976
|
+
setIsLoading(true);
|
|
4977
|
+
setErrorMsg(null);
|
|
4978
|
+
try {
|
|
4979
|
+
const result = await client.getRampTransaction(id);
|
|
4980
|
+
if (result.transactionVersion === void 0) throw new Error("This saved ramp cannot be restored.");
|
|
4981
|
+
setDirection(result.direction);
|
|
4982
|
+
await applyResult(result);
|
|
4983
|
+
} catch (error) {
|
|
4984
|
+
setErrorMsg(rampErrorMessage(error, "Could not resume the saved ramp."));
|
|
4985
|
+
} finally {
|
|
4986
|
+
setIsLoading(false);
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4571
4989
|
const directionRef = react.useRef(direction);
|
|
4572
4990
|
directionRef.current = direction;
|
|
4573
4991
|
function applySettlement(tx) {
|
|
@@ -4590,23 +5008,20 @@ function RampWidget({ onClose }) {
|
|
|
4590
5008
|
setRetrying(false);
|
|
4591
5009
|
}
|
|
4592
5010
|
}
|
|
5011
|
+
const workflowTerminal = !!workflow && ["completed", "failed", "refunded"].includes(workflow.lifecycleState ?? workflow.status);
|
|
4593
5012
|
react.useEffect(() => {
|
|
4594
5013
|
if (step !== "status" || !txId) return;
|
|
4595
|
-
if (txStatus && TERMINAL.includes(txStatus)) return;
|
|
5014
|
+
if (workflowTerminal || txStatus && TERMINAL.includes(txStatus)) return;
|
|
4596
5015
|
let active = true;
|
|
4597
5016
|
const id = setInterval(async () => {
|
|
4598
5017
|
try {
|
|
4599
5018
|
const tx = await client.getRampTransaction(txId);
|
|
4600
5019
|
if (!active) return;
|
|
5020
|
+
if (tx.transactionVersion !== void 0 && !acceptWorkflow(tx)) return;
|
|
4601
5021
|
setTxStatus(tx.status);
|
|
4602
5022
|
applySettlement(tx);
|
|
4603
|
-
const pending = tx.pendingSignature;
|
|
4604
|
-
if (pending
|
|
4605
|
-
signingRef.current = true;
|
|
4606
|
-
void resumeRef.current(txId, pending).finally(() => {
|
|
4607
|
-
signingRef.current = false;
|
|
4608
|
-
});
|
|
4609
|
-
}
|
|
5023
|
+
const pending = "pendingSignature" in tx ? tx.pendingSignature : void 0;
|
|
5024
|
+
if (pending) setLegacySignature(pending);
|
|
4610
5025
|
if (tx.kycUrl) setKycUrl(tx.kycUrl);
|
|
4611
5026
|
if (tx.depositInstructions) setDepositInstructions(tx.depositInstructions);
|
|
4612
5027
|
if (TERMINAL.includes(tx.status)) clearInterval(id);
|
|
@@ -4617,9 +5032,9 @@ function RampWidget({ onClose }) {
|
|
|
4617
5032
|
active = false;
|
|
4618
5033
|
clearInterval(id);
|
|
4619
5034
|
};
|
|
4620
|
-
}, [step, txId, txStatus, client]);
|
|
5035
|
+
}, [step, txId, txStatus, workflowTerminal, client]);
|
|
4621
5036
|
react.useEffect(() => {
|
|
4622
|
-
if (step !== "status" || !kycPending || kycApproved) return;
|
|
5037
|
+
if (workflow || step !== "status" || !kycPending || kycApproved || provider.toLowerCase() !== "abroad") return;
|
|
4623
5038
|
if (onboardingStatus === "awaiting_provider") return;
|
|
4624
5039
|
let active = true;
|
|
4625
5040
|
const check = async () => {
|
|
@@ -4635,14 +5050,15 @@ function RampWidget({ onClose }) {
|
|
|
4635
5050
|
active = false;
|
|
4636
5051
|
clearInterval(id);
|
|
4637
5052
|
};
|
|
4638
|
-
}, [step, kycPending, kycApproved, onboardingStatus, client]);
|
|
5053
|
+
}, [step, kycPending, kycApproved, onboardingStatus, workflow, client, provider]);
|
|
4639
5054
|
async function loadCountries(resetSelection) {
|
|
4640
5055
|
setCountriesLoading(true);
|
|
4641
5056
|
try {
|
|
4642
5057
|
const { countries: list } = await client.getRampCountries();
|
|
4643
5058
|
setCountries(list);
|
|
4644
|
-
const
|
|
4645
|
-
const
|
|
5059
|
+
const available = core.mergeRampCountries(list, routes);
|
|
5060
|
+
const first = available[0];
|
|
5061
|
+
const stillValid = available.some((c) => c.code === country);
|
|
4646
5062
|
if (first && (resetSelection || !stillValid)) {
|
|
4647
5063
|
setCountry(first.code);
|
|
4648
5064
|
if (first.currency) setCurrency(first.currency);
|
|
@@ -4667,11 +5083,22 @@ function RampWidget({ onClose }) {
|
|
|
4667
5083
|
try {
|
|
4668
5084
|
await loadCountries(false);
|
|
4669
5085
|
if (step === "select_route") {
|
|
4670
|
-
const result = await
|
|
5086
|
+
const result = await quoteRampOptions(
|
|
5087
|
+
client,
|
|
5088
|
+
{
|
|
5089
|
+
country,
|
|
5090
|
+
amount: Number(amount),
|
|
5091
|
+
amountExact: amount,
|
|
5092
|
+
currency,
|
|
5093
|
+
direction
|
|
5094
|
+
},
|
|
5095
|
+
routes
|
|
5096
|
+
);
|
|
4671
5097
|
setQuotes(result.quotes ?? []);
|
|
4672
5098
|
setRequirementsRequired(result.requirementsRequired ?? []);
|
|
4673
5099
|
} else if (step === "status" && txId) {
|
|
4674
5100
|
const tx = await client.getRampTransaction(txId);
|
|
5101
|
+
if (tx.transactionVersion !== void 0 && !acceptWorkflow(tx)) return;
|
|
4675
5102
|
setTxStatus(tx.status);
|
|
4676
5103
|
applySettlement(tx);
|
|
4677
5104
|
if (tx.kycUrl) setKycUrl(tx.kycUrl);
|
|
@@ -4683,11 +5110,16 @@ function RampWidget({ onClose }) {
|
|
|
4683
5110
|
}
|
|
4684
5111
|
}
|
|
4685
5112
|
function resetToInput({ keepMessage = false } = {}) {
|
|
5113
|
+
writeRampStorage(storageKey, null);
|
|
5114
|
+
writeRampStorage(`${storageKey}:selection`, "1");
|
|
4686
5115
|
setStep("input");
|
|
4687
5116
|
setQuotes([]);
|
|
4688
5117
|
setRequirementsRequired([]);
|
|
4689
5118
|
setSelectedQuote(null);
|
|
4690
5119
|
setFieldValues({});
|
|
5120
|
+
setWorkflow(null);
|
|
5121
|
+
workflowRef.current = null;
|
|
5122
|
+
setLegacySignature(void 0);
|
|
4691
5123
|
setTxId(null);
|
|
4692
5124
|
setProvider("");
|
|
4693
5125
|
setKycUrl(null);
|
|
@@ -4703,8 +5135,20 @@ function RampWidget({ onClose }) {
|
|
|
4703
5135
|
setNoticeMsg(null);
|
|
4704
5136
|
if (!keepMessage) setErrorMsg(null);
|
|
4705
5137
|
}
|
|
5138
|
+
function dismissSavedRamp(id) {
|
|
5139
|
+
if (completionLocked.current) return;
|
|
5140
|
+
const pending = savedRampsOf(storageKey).filter((ramp) => ramp.txId !== id);
|
|
5141
|
+
writeRampStorage(`${storageKey}:saved`, JSON.stringify(pending));
|
|
5142
|
+
writeRampStorage(`${storageKey}:hidden`, JSON.stringify([.../* @__PURE__ */ new Set([...hiddenRampsOf(storageKey), id])]));
|
|
5143
|
+
if (readRampStorage(storageKey) === id) writeRampStorage(storageKey, null);
|
|
5144
|
+
setSavedRamps(pending);
|
|
5145
|
+
if (txId === id) resetToInput();
|
|
5146
|
+
setNoticeMsg(
|
|
5147
|
+
"Removed from this tab\u2019s pending list. The operation may still be active at the provider; no payment was cancelled or refunded."
|
|
5148
|
+
);
|
|
5149
|
+
}
|
|
4706
5150
|
async function resumeWithSignature(id, ps) {
|
|
4707
|
-
const outcome = await signTx(ps.unsignedXdr);
|
|
5151
|
+
const outcome = await client.signTx(ps.unsignedXdr, { skipSponsorship: ps.action === "sep10" });
|
|
4708
5152
|
if (outcome.status === "cosign_pending") {
|
|
4709
5153
|
setErrorMsg("Approve the signature on your device, then resume.");
|
|
4710
5154
|
setStep("error");
|
|
@@ -4717,20 +5161,15 @@ function RampWidget({ onClose }) {
|
|
|
4717
5161
|
}
|
|
4718
5162
|
const result = await client.submitRampSignature(id, {
|
|
4719
5163
|
signedXdr: outcome.signedXdr,
|
|
4720
|
-
// The CCTP actions are not in the generated schema until it is regenerated.
|
|
4721
5164
|
action: ps.action
|
|
4722
5165
|
});
|
|
4723
5166
|
await applyResult(result);
|
|
4724
5167
|
}
|
|
4725
|
-
const resumeRef = react.useRef(resumeWithSignature);
|
|
4726
|
-
resumeRef.current = resumeWithSignature;
|
|
4727
5168
|
async function applyResult(result) {
|
|
5169
|
+
if ("transactionVersion" in result && result.transactionVersion !== void 0 && !acceptWorkflow(result)) return;
|
|
4728
5170
|
setTxId(result.txId);
|
|
4729
5171
|
setProvider(result.provider);
|
|
4730
|
-
|
|
4731
|
-
await resumeWithSignature(result.txId, result.pendingSignature);
|
|
4732
|
-
return;
|
|
4733
|
-
}
|
|
5172
|
+
setLegacySignature(result.pendingSignature);
|
|
4734
5173
|
setKycUrl(result.kycUrl ?? null);
|
|
4735
5174
|
setTosUrl(result.tosUrl ?? null);
|
|
4736
5175
|
setOnboardingStatus(result.onboardingStatus);
|
|
@@ -4742,14 +5181,21 @@ function RampWidget({ onClose }) {
|
|
|
4742
5181
|
setStep("status");
|
|
4743
5182
|
}
|
|
4744
5183
|
async function loadQuotes() {
|
|
4745
|
-
const result = await
|
|
5184
|
+
const result = await quoteRampOptions(
|
|
5185
|
+
client,
|
|
5186
|
+
{
|
|
5187
|
+
country,
|
|
5188
|
+
amount: Number(amount),
|
|
5189
|
+
amountExact: amount,
|
|
5190
|
+
currency,
|
|
5191
|
+
direction
|
|
5192
|
+
},
|
|
5193
|
+
routes
|
|
5194
|
+
);
|
|
4746
5195
|
const list = result.quotes ?? [];
|
|
4747
5196
|
const locked = result.requirementsRequired ?? [];
|
|
4748
5197
|
if (list.length === 0 && locked.length === 0) {
|
|
4749
|
-
|
|
4750
|
-
setErrorMsg(
|
|
4751
|
-
down.length > 0 ? `${down.join(", ")} ${down.length === 1 ? "is" : "are"} temporarily unavailable. Please try again later.` : `No ramp providers available for ${country} yet.`
|
|
4752
|
-
);
|
|
5198
|
+
setErrorMsg(unavailableRampMessage(country, result.unavailable ?? []));
|
|
4753
5199
|
setStep("error");
|
|
4754
5200
|
return false;
|
|
4755
5201
|
}
|
|
@@ -4795,19 +5241,40 @@ function RampWidget({ onClose }) {
|
|
|
4795
5241
|
setIsLoading(true);
|
|
4796
5242
|
setErrorMsg(null);
|
|
4797
5243
|
try {
|
|
4798
|
-
const
|
|
4799
|
-
|
|
4800
|
-
|
|
5244
|
+
const body = {
|
|
5245
|
+
quoteId: quote.quoteId,
|
|
5246
|
+
amount: quote.fiatAmount ?? Number(amount),
|
|
5247
|
+
...quote.terms ? { amountExact: quote.terms.fiatAmount } : {},
|
|
5248
|
+
currency,
|
|
5249
|
+
country,
|
|
5250
|
+
...walletAddress ? { walletAddress } : {}
|
|
5251
|
+
};
|
|
4801
5252
|
const extraFields = {};
|
|
4802
5253
|
for (const f of requiredFieldsOf(quote)) {
|
|
4803
5254
|
const val = (fieldValues[f.key] ?? "").trim();
|
|
4804
5255
|
if (!val) continue;
|
|
4805
|
-
if (f.bankType)
|
|
4806
|
-
|
|
5256
|
+
if (f.bankType) {
|
|
5257
|
+
if (isBankType(f.bankType)) body.bankDetails = { type: f.bankType, value: val };
|
|
5258
|
+
} else if (f.key === "email" || f.key === "fullName" || f.key === "taxId" || f.key === "qrCode") body[f.key] = val;
|
|
4807
5259
|
else extraFields[f.key] = val;
|
|
4808
5260
|
}
|
|
4809
|
-
if (
|
|
4810
|
-
|
|
5261
|
+
if (quote.route)
|
|
5262
|
+
body.fields = Object.fromEntries(
|
|
5263
|
+
requiredFieldsOf(quote).map((field) => [field.key, (fieldValues[field.key] ?? "").trim()]).filter(([, value]) => value)
|
|
5264
|
+
);
|
|
5265
|
+
else if (Object.keys(extraFields).length > 0) body.fields = extraFields;
|
|
5266
|
+
const onrampBody = {
|
|
5267
|
+
quoteId: body.quoteId,
|
|
5268
|
+
amount: body.amount,
|
|
5269
|
+
currency: body.currency,
|
|
5270
|
+
country: body.country,
|
|
5271
|
+
...body.amountExact !== void 0 ? { amountExact: body.amountExact } : {},
|
|
5272
|
+
...body.walletAddress !== void 0 ? { walletAddress: body.walletAddress } : {},
|
|
5273
|
+
...body.email !== void 0 ? { email: body.email } : {},
|
|
5274
|
+
...body.fullName !== void 0 ? { fullName: body.fullName } : {},
|
|
5275
|
+
...body.fields !== void 0 ? { fields: body.fields } : {}
|
|
5276
|
+
};
|
|
5277
|
+
const result = direction === "onramp" ? await client.createOnRamp(onrampBody) : await client.createOffRamp(body);
|
|
4811
5278
|
await applyResult(result);
|
|
4812
5279
|
} catch (e) {
|
|
4813
5280
|
const requirement = requiredRampKyc(e);
|
|
@@ -4857,8 +5324,24 @@ function RampWidget({ onClose }) {
|
|
|
4857
5324
|
function handleOpenTos() {
|
|
4858
5325
|
if (tosUrl) window.open(tosUrl, "_blank", "noopener,noreferrer");
|
|
4859
5326
|
}
|
|
5327
|
+
async function handleLegacySignature() {
|
|
5328
|
+
if (!txId || !legacySignature || completionLocked.current) return;
|
|
5329
|
+
completionLocked.current = true;
|
|
5330
|
+
setCompleting(true);
|
|
5331
|
+
setErrorMsg(null);
|
|
5332
|
+
try {
|
|
5333
|
+
await resumeWithSignature(txId, legacySignature);
|
|
5334
|
+
} catch (e) {
|
|
5335
|
+
setErrorMsg(rampErrorMessage(e, "Failed to submit the signature."));
|
|
5336
|
+
setStep("error");
|
|
5337
|
+
} finally {
|
|
5338
|
+
completionLocked.current = false;
|
|
5339
|
+
setCompleting(false);
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
4860
5342
|
async function handleCompleteWithdraw() {
|
|
4861
|
-
if (!txId) return;
|
|
5343
|
+
if (!txId || completionLocked.current) return;
|
|
5344
|
+
completionLocked.current = true;
|
|
4862
5345
|
setCompleting(true);
|
|
4863
5346
|
setErrorMsg(null);
|
|
4864
5347
|
try {
|
|
@@ -4875,11 +5358,12 @@ function RampWidget({ onClose }) {
|
|
|
4875
5358
|
msg.includes("KYC") ? "Finish KYC at the provider first, then try again." : msg || "Failed to complete the withdrawal."
|
|
4876
5359
|
);
|
|
4877
5360
|
} finally {
|
|
5361
|
+
completionLocked.current = false;
|
|
4878
5362
|
setCompleting(false);
|
|
4879
5363
|
}
|
|
4880
5364
|
}
|
|
4881
5365
|
const kycBlocking = kycPending && !kycApproved;
|
|
4882
|
-
const canComplete = direction === "offramp" && step === "status" && txStatus !== "completed" && !stellarTxHash && !txHash && !kycPending;
|
|
5366
|
+
const canComplete = !workflow && !legacySignature && direction === "offramp" && step === "status" && txStatus !== "completed" && txStatus !== "failed" && !stellarTxHash && !txHash && !kycPending;
|
|
4883
5367
|
const flowSteps = flowStepsOf(quotes, selectedQuote);
|
|
4884
5368
|
const flowStepIndex = flowSteps.indexOf(STEP_LABEL[step] ?? "");
|
|
4885
5369
|
if (pendingKyc?.type === "FORM") {
|
|
@@ -4937,6 +5421,20 @@ function RampWidget({ onClose }) {
|
|
|
4937
5421
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-overlay", style: overlayStyle, onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4938
5422
|
RampWidgetTemplate,
|
|
4939
5423
|
{
|
|
5424
|
+
workflowContent: workflow ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5425
|
+
RampWorkflow,
|
|
5426
|
+
{
|
|
5427
|
+
client,
|
|
5428
|
+
snapshot: workflow,
|
|
5429
|
+
onBusyChange: (busy) => {
|
|
5430
|
+
completionLocked.current = busy;
|
|
5431
|
+
setCompleting(busy);
|
|
5432
|
+
},
|
|
5433
|
+
onChange: (next) => {
|
|
5434
|
+
if (acceptWorkflow(next)) setTxStatus(next.status);
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
) : legacySignature && step === "status" && txStatus !== "completed" && txStatus !== "failed" ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: completing, onClick: () => void handleLegacySignature(), children: "Authorize wallet request" }) : void 0,
|
|
4940
5438
|
theme,
|
|
4941
5439
|
accentColor,
|
|
4942
5440
|
styleOverrides,
|
|
@@ -4973,9 +5471,18 @@ function RampWidget({ onClose }) {
|
|
|
4973
5471
|
depositInstructions,
|
|
4974
5472
|
canComplete,
|
|
4975
5473
|
completing,
|
|
5474
|
+
savedRamps,
|
|
5475
|
+
onResumeRamp: resumeSavedRamp,
|
|
5476
|
+
onDismissRamp: dismissSavedRamp,
|
|
5477
|
+
onDismissCurrentRamp: workflow ? () => dismissSavedRamp(workflow.txId) : void 0,
|
|
5478
|
+
onExploreRoutes: workflow ? () => {
|
|
5479
|
+
if (!completionLocked.current) resetToInput();
|
|
5480
|
+
} : void 0,
|
|
4976
5481
|
errorMsg,
|
|
4977
5482
|
noticeMsg,
|
|
4978
|
-
onDirectionChange:
|
|
5483
|
+
onDirectionChange: (next) => {
|
|
5484
|
+
setDirection(next);
|
|
5485
|
+
},
|
|
4979
5486
|
onAmountChange: (next) => {
|
|
4980
5487
|
setAmount(next);
|
|
4981
5488
|
setErrorMsg(null);
|
|
@@ -5008,49 +5515,6 @@ function walletNotReadyReason(wallet, chain) {
|
|
|
5008
5515
|
}
|
|
5009
5516
|
return null;
|
|
5010
5517
|
}
|
|
5011
|
-
|
|
5012
|
-
// src/lib/qr-code/index.tsx
|
|
5013
|
-
var import_ErrorCorrectLevel = __toESM(require_ErrorCorrectLevel());
|
|
5014
|
-
var import_QRCode = __toESM(require_QRCode());
|
|
5015
|
-
var QRCodeSvg = react.forwardRef(function QRCodeSvg2({ bgColor, bgD, fgD, fgColor, size, title, viewBoxSize, xmlns = "http://www.w3.org/2000/svg", ...props }, ref) {
|
|
5016
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...props, height: size, ref, viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`, width: size, xmlns, children: [
|
|
5017
|
-
title ? /* @__PURE__ */ jsxRuntime.jsx("title", { children: title }) : null,
|
|
5018
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: bgD, fill: bgColor }),
|
|
5019
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: fgD, fill: fgColor })
|
|
5020
|
-
] });
|
|
5021
|
-
});
|
|
5022
|
-
QRCodeSvg.displayName = "QRCodeSvg";
|
|
5023
|
-
function bytesToBinaryString(bytes) {
|
|
5024
|
-
return bytes.map((b) => String.fromCharCode(b & 255)).join("");
|
|
5025
|
-
}
|
|
5026
|
-
function encodeStringToUtf8Bytes(input) {
|
|
5027
|
-
return Array.from(new TextEncoder().encode(input));
|
|
5028
|
-
}
|
|
5029
|
-
var QRCode = react.forwardRef(function QRCode2({ bgColor = "#FFFFFF", fgColor = "#000000", level = "L", size = 256, value, ...props }, ref) {
|
|
5030
|
-
const qrcode = new import_QRCode.default(-1, import_ErrorCorrectLevel.default[level]);
|
|
5031
|
-
const utf8Bytes = encodeStringToUtf8Bytes(value);
|
|
5032
|
-
const binaryString = bytesToBinaryString(utf8Bytes);
|
|
5033
|
-
qrcode.addData(binaryString, "Byte");
|
|
5034
|
-
qrcode.make();
|
|
5035
|
-
const cells = qrcode.modules;
|
|
5036
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5037
|
-
QRCodeSvg,
|
|
5038
|
-
{
|
|
5039
|
-
...props,
|
|
5040
|
-
bgColor,
|
|
5041
|
-
bgD: cells.map(
|
|
5042
|
-
(row, rowIndex) => row.map((cell, cellIndex) => !cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
|
|
5043
|
-
).join(" "),
|
|
5044
|
-
fgColor,
|
|
5045
|
-
fgD: cells.map(
|
|
5046
|
-
(row, rowIndex) => row.map((cell, cellIndex) => cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
|
|
5047
|
-
).join(" "),
|
|
5048
|
-
ref,
|
|
5049
|
-
size,
|
|
5050
|
-
viewBoxSize: cells.length
|
|
5051
|
-
}
|
|
5052
|
-
);
|
|
5053
|
-
});
|
|
5054
5518
|
function PollarLogo(props) {
|
|
5055
5519
|
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "45", height: "48", viewBox: "0 0 45 48", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: [
|
|
5056
5520
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -5091,6 +5555,8 @@ function ReceiveModalTemplate({
|
|
|
5091
5555
|
onSelectChain,
|
|
5092
5556
|
copied,
|
|
5093
5557
|
notReadyReason,
|
|
5558
|
+
recoveryLocked,
|
|
5559
|
+
onSetupRecovery,
|
|
5094
5560
|
onCopy,
|
|
5095
5561
|
onClose
|
|
5096
5562
|
}) {
|
|
@@ -5109,7 +5575,10 @@ function ReceiveModalTemplate({
|
|
|
5109
5575
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-modal-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 2l12 12M14 2L2 14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) }) })
|
|
5110
5576
|
] }),
|
|
5111
5577
|
chains && onSelectChain && /* @__PURE__ */ jsxRuntime.jsx(ChainSelect, { value: selectedChain, options: chains, onChange: onSelectChain }),
|
|
5112
|
-
|
|
5578
|
+
recoveryLocked ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-receive-locked", role: "alert", children: [
|
|
5579
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: "Set up a way to recover this wallet before receiving funds. Without one, losing this device means losing whatever you receive, and nobody can get it back." }),
|
|
5580
|
+
onSetupRecovery && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-receive-copy-btn", onClick: onSetupRecovery, children: "Set up recovery" })
|
|
5581
|
+
] }) : walletAddress ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5113
5582
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-receive-qr", children: [
|
|
5114
5583
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-receive-network-badge", children: [
|
|
5115
5584
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-receive-network-dot", "aria-hidden": true }),
|
|
@@ -5183,7 +5652,7 @@ function ReceiveModalTemplate({
|
|
|
5183
5652
|
);
|
|
5184
5653
|
}
|
|
5185
5654
|
function ReceiveModal({ onClose }) {
|
|
5186
|
-
const { wallet, wallets, styles } = usePollar();
|
|
5655
|
+
const { wallet, wallets, styles, recoveryLocked, openRecoveryModal } = usePollar();
|
|
5187
5656
|
const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
|
|
5188
5657
|
const [copied, setCopied] = react.useState(false);
|
|
5189
5658
|
const copyTimerRef = react.useRef(null);
|
|
@@ -5225,6 +5694,11 @@ function ReceiveModal({ onClose }) {
|
|
|
5225
5694
|
setSelectedChain(chain);
|
|
5226
5695
|
},
|
|
5227
5696
|
notReadyReason,
|
|
5697
|
+
recoveryLocked,
|
|
5698
|
+
onSetupRecovery: () => {
|
|
5699
|
+
onClose();
|
|
5700
|
+
openRecoveryModal();
|
|
5701
|
+
},
|
|
5228
5702
|
copied,
|
|
5229
5703
|
onCopy: handleCopy,
|
|
5230
5704
|
onClose
|
|
@@ -5748,6 +6222,11 @@ function SendModalTemplate({
|
|
|
5748
6222
|
);
|
|
5749
6223
|
}
|
|
5750
6224
|
var MAX_MEMO_ID = 18446744073709551615n;
|
|
6225
|
+
var SOLANA_FEE_MARGIN_LAMPORTS = 10000n;
|
|
6226
|
+
function solanaMaxAfterFee(available) {
|
|
6227
|
+
const lamports = BigInt(core.toBaseUnits(available, 9)) - SOLANA_FEE_MARGIN_LAMPORTS;
|
|
6228
|
+
return core.fromBaseUnits((lamports > 0n ? lamports : 0n).toString(), 9);
|
|
6229
|
+
}
|
|
5751
6230
|
function parseMemo(type, raw) {
|
|
5752
6231
|
const value = type === "id" ? raw.trim() : raw;
|
|
5753
6232
|
if (value.trim() === "") return void 0;
|
|
@@ -5907,7 +6386,15 @@ function SendModal({ onClose }) {
|
|
|
5907
6386
|
}
|
|
5908
6387
|
function handleMax() {
|
|
5909
6388
|
if (selectedAsset?.available !== null && selectedAsset?.available !== void 0) {
|
|
5910
|
-
|
|
6389
|
+
const paysOwnSolFee = selectedChain === "SOLANA" && selectedAsset.type === "native" && selectedAsset.sponsored !== true;
|
|
6390
|
+
let max = selectedAsset.available;
|
|
6391
|
+
if (paysOwnSolFee) {
|
|
6392
|
+
try {
|
|
6393
|
+
max = solanaMaxAfterFee(selectedAsset.available);
|
|
6394
|
+
} catch {
|
|
6395
|
+
}
|
|
6396
|
+
}
|
|
6397
|
+
setAmount(max);
|
|
5911
6398
|
setFormError("");
|
|
5912
6399
|
}
|
|
5913
6400
|
}
|
|
@@ -7106,9 +7593,12 @@ function EarnModal({ onClose }) {
|
|
|
7106
7593
|
var nonEmpty2 = (value) => typeof value === "string" && !!value.trim();
|
|
7107
7594
|
function requiredCardStep(error) {
|
|
7108
7595
|
if (!error || typeof error !== "object") return null;
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7596
|
+
if (!("code" in error) || error.code !== "SDK_CARDS_REQUIREMENT_REQUIRED") return null;
|
|
7597
|
+
const body = "body" in error ? error.body : void 0;
|
|
7598
|
+
if (typeof body !== "object" || body === null) return null;
|
|
7599
|
+
const cardProviderId = "cardProviderId" in body ? body.cardProviderId : void 0;
|
|
7600
|
+
const requirementType = "requirementType" in body ? body.requirementType : void 0;
|
|
7601
|
+
const optionId = "optionId" in body ? body.optionId : void 0;
|
|
7112
7602
|
if (!nonEmpty2(cardProviderId) || !nonEmpty2(optionId)) return null;
|
|
7113
7603
|
if (requirementType === "KYC" || requirementType === "FORM" || requirementType === "REGISTRY_CHECK" || requirementType === "PROVIDER_REGISTRATION") {
|
|
7114
7604
|
return { cardProviderId, type: requirementType, optionId };
|
|
@@ -7762,15 +8252,17 @@ function CardModal({ onClose }) {
|
|
|
7762
8252
|
transactions === null && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-card-note", children: "Loading..." }),
|
|
7763
8253
|
transactions?.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-card-note", children: "No movements yet." }),
|
|
7764
8254
|
transactions?.map((tx) => {
|
|
7765
|
-
const
|
|
7766
|
-
const
|
|
8255
|
+
const localAmount = typeof tx.purchase?.localAmount === "number" ? tx.purchase.localAmount : null;
|
|
8256
|
+
const localCurrency = typeof tx.purchase?.localCurrency === "string" ? tx.purchase.localCurrency : null;
|
|
8257
|
+
const purchaseStatus = typeof tx.purchase?.status === "string" ? tx.purchase.status : null;
|
|
8258
|
+
const local = localAmount && localCurrency && localCurrency !== "USD" ? money(String(localAmount), localCurrency) : null;
|
|
7767
8259
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-card-tx", children: [
|
|
7768
8260
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
7769
8261
|
tx.merchantName ?? TX_LABELS[tx.type] ?? tx.type,
|
|
7770
8262
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-card-tx-meta", children: [
|
|
7771
8263
|
new Date(tx.occurredAt).toLocaleDateString(),
|
|
7772
8264
|
tx.merchantName ? ` \xB7 ${TX_LABELS[tx.type] ?? tx.type}` : "",
|
|
7773
|
-
|
|
8265
|
+
purchaseStatus && purchaseStatus !== "completed" ? ` \xB7 ${purchaseStatus}` : ""
|
|
7774
8266
|
] })
|
|
7775
8267
|
] }),
|
|
7776
8268
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-card-tx-amount", "data-direction": tx.direction ?? void 0, children: [
|
|
@@ -8169,7 +8661,8 @@ function useCosign() {
|
|
|
8169
8661
|
revokeDevice: react.useCallback(
|
|
8170
8662
|
(deviceId, options) => getClient().cosign.revokeDevice(deviceId, options),
|
|
8171
8663
|
[getClient]
|
|
8172
|
-
)
|
|
8664
|
+
),
|
|
8665
|
+
switchCustody: react.useCallback((to) => getClient().cosign.switchCustody(to), [getClient])
|
|
8173
8666
|
};
|
|
8174
8667
|
}
|
|
8175
8668
|
var OPERATION_LABELS = {
|
|
@@ -8191,6 +8684,15 @@ function expiresIn(request) {
|
|
|
8191
8684
|
const m = Math.floor(ms % 36e5 / 6e4);
|
|
8192
8685
|
return h > 0 ? `${h}h ${m}m` : `${m}m`;
|
|
8193
8686
|
}
|
|
8687
|
+
function readableAmount(details) {
|
|
8688
|
+
const { amount, decimals, symbol } = details;
|
|
8689
|
+
if (typeof amount !== "string" || typeof decimals !== "number" || typeof symbol !== "string") return null;
|
|
8690
|
+
try {
|
|
8691
|
+
return { amount: core.fromBaseUnits(amount, decimals), symbol };
|
|
8692
|
+
} catch {
|
|
8693
|
+
return null;
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8194
8696
|
function detailRows(request) {
|
|
8195
8697
|
const details = request.details ?? {};
|
|
8196
8698
|
const rows = [];
|
|
@@ -8202,8 +8704,9 @@ function detailRows(request) {
|
|
|
8202
8704
|
]);
|
|
8203
8705
|
};
|
|
8204
8706
|
push("Destination", details.destination);
|
|
8205
|
-
|
|
8206
|
-
push("
|
|
8707
|
+
const readable = readableAmount(details);
|
|
8708
|
+
push("Amount", readable ? `${readable.amount} ${readable.symbol}` : details.amount);
|
|
8709
|
+
push("Asset", details.asset ?? details.assetCode ?? readable?.symbol);
|
|
8207
8710
|
push("Network", request.network);
|
|
8208
8711
|
return rows;
|
|
8209
8712
|
}
|
|
@@ -8278,7 +8781,6 @@ function CosignModalTemplate({
|
|
|
8278
8781
|
current && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-detail", children: [
|
|
8279
8782
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-detail-title", children: labelOf(current) }),
|
|
8280
8783
|
current.summary && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-muted", children: current.summary }),
|
|
8281
|
-
/* @__PURE__ */ jsxRuntime.jsx(GroupSteps, { request: current }),
|
|
8282
8784
|
/* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "pollar-cosign-rows", children: [
|
|
8283
8785
|
detailRows(current).map(([k, v]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-row", children: [
|
|
8284
8786
|
/* @__PURE__ */ jsxRuntime.jsx("dt", { children: k }),
|
|
@@ -8329,22 +8831,6 @@ function CosignModalTemplate({
|
|
|
8329
8831
|
}
|
|
8330
8832
|
);
|
|
8331
8833
|
}
|
|
8332
|
-
function GroupSteps({ request }) {
|
|
8333
|
-
const group = core.cosignGroupOf(request);
|
|
8334
|
-
if (!group || group.steps.length < 2) return null;
|
|
8335
|
-
const stepKey = core.cosignStepKeyOf(request);
|
|
8336
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-group", children: [
|
|
8337
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-group-title", children: group.title }),
|
|
8338
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-muted", children: "Approving covers these steps; each one is checked before it is signed." }),
|
|
8339
|
-
/* @__PURE__ */ jsxRuntime.jsx("ol", { className: "pollar-cosign-group-steps", children: group.steps.map((step) => {
|
|
8340
|
-
const isThis = stepKey !== null && core.stepInGroup({ steps: [step] }, stepKey);
|
|
8341
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: isThis ? "pollar-cosign-group-step-current" : void 0, children: [
|
|
8342
|
-
step.label,
|
|
8343
|
-
isThis && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-cosign-muted", children: " (this one)" })
|
|
8344
|
-
] }, step.key);
|
|
8345
|
-
}) })
|
|
8346
|
-
] });
|
|
8347
|
-
}
|
|
8348
8834
|
function CosignModal({ onClose }) {
|
|
8349
8835
|
const { getClient, styles, network } = usePollar();
|
|
8350
8836
|
const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
|
|
@@ -8427,6 +8913,493 @@ function CosignModal({ onClose }) {
|
|
|
8427
8913
|
}
|
|
8428
8914
|
) });
|
|
8429
8915
|
}
|
|
8916
|
+
var CHAIN_NAME3 = { STELLAR: "Stellar", SOLANA: "Solana", POLYGON: "Polygon" };
|
|
8917
|
+
var short = (address) => `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
8918
|
+
var SELF_CUSTODY_RISKS = "In a self-custody wallet only your device can sign. If you lose this device and every recovery method, nobody can recover the wallet or its funds, not the app and not Pollar. Nobody can undo or freeze a transaction you sign.";
|
|
8919
|
+
function CustodyMigrationModalTemplate(p) {
|
|
8920
|
+
const cssVars = buildModalCssVars(p.theme, p.accentColor, p.styleOverrides);
|
|
8921
|
+
const [pick, setPick] = react.useState(p.state.choice ? null : p.state.toCustody);
|
|
8922
|
+
const [risksAccepted, setRisksAccepted] = react.useState(false);
|
|
8923
|
+
const custody = p.state.choice ? pick : p.state.toCustody;
|
|
8924
|
+
const canMove = !!custody && (custody !== "SELF_CUSTODY" || risksAccepted) && !p.busy;
|
|
8925
|
+
const moving = p.state.wallets.filter((w) => w.required);
|
|
8926
|
+
const changing = moving.filter((w) => w.addressChanges);
|
|
8927
|
+
const failed = moving.some((w) => w.status === "FAILED");
|
|
8928
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-card pollar-recovery-modal", "data-theme": p.theme, style: cssVars, children: [
|
|
8929
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-header", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "pollar-modal-title", children: "Move your wallets to this device" }) }),
|
|
8930
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-body", children: [
|
|
8931
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: "Until now Pollar held the keys of your wallets. This app now keeps wallets on its users' devices, so they move here, with everything they hold." }),
|
|
8932
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
|
|
8933
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "What moves" }),
|
|
8934
|
+
moving.map((w) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-item", children: [
|
|
8935
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
8936
|
+
CHAIN_NAME3[w.chain] ?? w.chain,
|
|
8937
|
+
" \xB7 ",
|
|
8938
|
+
short(w.address)
|
|
8939
|
+
] }),
|
|
8940
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-recovery-tag", children: w.addressChanges ? "New address" : "Same address" })
|
|
8941
|
+
] }, w.chain))
|
|
8942
|
+
] }),
|
|
8943
|
+
changing.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: [
|
|
8944
|
+
"Your ",
|
|
8945
|
+
changing.map((w) => CHAIN_NAME3[w.chain] ?? w.chain).join(" and "),
|
|
8946
|
+
" address changes: every token and the balance go to a new address on this device. Stop sharing the old one; anything sent there later needs help from support to recover."
|
|
8947
|
+
] }),
|
|
8948
|
+
p.state.choice && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
|
|
8949
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Choose how your wallet signs" }),
|
|
8950
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-recovery-check", children: [
|
|
8951
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "radio", name: "custody", checked: pick === "COSIGNED", onChange: () => setPick("COSIGNED") }),
|
|
8952
|
+
" ",
|
|
8953
|
+
"Co-signed: you and the app approve every signature together, and the wallet can be recovered with help."
|
|
8954
|
+
] }),
|
|
8955
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-recovery-check", children: [
|
|
8956
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "radio", name: "custody", checked: pick === "SELF_CUSTODY", onChange: () => setPick("SELF_CUSTODY") }),
|
|
8957
|
+
" ",
|
|
8958
|
+
"Self-custody: only your device signs. Nobody else can move or recover your funds."
|
|
8959
|
+
] })
|
|
8960
|
+
] }),
|
|
8961
|
+
custody === "SELF_CUSTODY" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
8962
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: SELF_CUSTODY_RISKS }),
|
|
8963
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-recovery-check", children: [
|
|
8964
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: risksAccepted, onChange: (e) => setRisksAccepted(e.target.checked) }),
|
|
8965
|
+
" I understand and accept these risks"
|
|
8966
|
+
] })
|
|
8967
|
+
] }),
|
|
8968
|
+
failed && !p.error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-error", role: "alert", children: "The last attempt did not finish. What did not move is still where it was; try again." }),
|
|
8969
|
+
p.blockedReasons && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: [
|
|
8970
|
+
"A wallet cannot move while something still uses it: ",
|
|
8971
|
+
p.blockedReasons.join(", "),
|
|
8972
|
+
". Let it finish and try again."
|
|
8973
|
+
] }),
|
|
8974
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8975
|
+
"button",
|
|
8976
|
+
{
|
|
8977
|
+
type: "button",
|
|
8978
|
+
className: "pollar-recovery-btn pollar-recovery-btn-primary",
|
|
8979
|
+
onClick: () => custody && p.onMove(p.state.choice ? custody : void 0),
|
|
8980
|
+
disabled: !canMove,
|
|
8981
|
+
children: p.busy ? "Moving your wallets..." : "Move my wallets"
|
|
8982
|
+
}
|
|
8983
|
+
),
|
|
8984
|
+
p.blockedReasons && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-recovery-btn pollar-recovery-btn-quiet", onClick: p.onLater, disabled: p.busy, children: "Continue for now" }),
|
|
8985
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-recovery-btn pollar-recovery-btn-quiet", onClick: p.onLogout, disabled: p.busy, children: "Log out" }),
|
|
8986
|
+
p.error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-error", role: "alert", children: p.error })
|
|
8987
|
+
] }),
|
|
8988
|
+
/* @__PURE__ */ jsxRuntime.jsx(PollarModalFooter, {})
|
|
8989
|
+
] });
|
|
8990
|
+
}
|
|
8991
|
+
function CustodyMigrationModal({ state, onMoved, onLater }) {
|
|
8992
|
+
const { getClient, styles } = usePollar();
|
|
8993
|
+
const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
|
|
8994
|
+
const [busy, setBusy] = react.useState(false);
|
|
8995
|
+
const [error, setError] = react.useState(null);
|
|
8996
|
+
const [blockedReasons, setBlockedReasons] = react.useState(null);
|
|
8997
|
+
async function move(custody) {
|
|
8998
|
+
setBusy(true);
|
|
8999
|
+
setError(null);
|
|
9000
|
+
setBlockedReasons(null);
|
|
9001
|
+
try {
|
|
9002
|
+
await getClient().cosign.migrateWallets(custody);
|
|
9003
|
+
onMoved();
|
|
9004
|
+
} catch (err) {
|
|
9005
|
+
if (core.isPollarApiError(err) && err.code === "SDK_CUSTODY_MIGRATION_BLOCKED") {
|
|
9006
|
+
const reasons = err.body.reasons;
|
|
9007
|
+
setBlockedReasons(Array.isArray(reasons) ? reasons.map(String) : []);
|
|
9008
|
+
return;
|
|
9009
|
+
}
|
|
9010
|
+
setError(core.transactionErrorMessage(core.isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null));
|
|
9011
|
+
} finally {
|
|
9012
|
+
setBusy(false);
|
|
9013
|
+
}
|
|
9014
|
+
}
|
|
9015
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-overlay", style: overlayStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9016
|
+
CustodyMigrationModalTemplate,
|
|
9017
|
+
{
|
|
9018
|
+
theme,
|
|
9019
|
+
accentColor,
|
|
9020
|
+
styleOverrides,
|
|
9021
|
+
state,
|
|
9022
|
+
busy,
|
|
9023
|
+
error,
|
|
9024
|
+
blockedReasons,
|
|
9025
|
+
onMove: (custody) => void move(custody),
|
|
9026
|
+
onLater,
|
|
9027
|
+
onLogout: () => void getClient().logout()
|
|
9028
|
+
}
|
|
9029
|
+
) });
|
|
9030
|
+
}
|
|
9031
|
+
function CloseIcon3() {
|
|
9032
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m3 3 8 8M11 3l-8 8", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) });
|
|
9033
|
+
}
|
|
9034
|
+
var shortKey = (key) => `${key.slice(0, 6)}...${key.slice(-4)}`;
|
|
9035
|
+
function Button(props) {
|
|
9036
|
+
const running = props.busy === props.id;
|
|
9037
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9038
|
+
"button",
|
|
9039
|
+
{
|
|
9040
|
+
type: "button",
|
|
9041
|
+
className: `pollar-recovery-btn pollar-recovery-btn-${props.kind ?? "quiet"}`,
|
|
9042
|
+
onClick: props.onClick,
|
|
9043
|
+
disabled: props.disabled || props.busy !== null,
|
|
9044
|
+
children: running ? props.busyLabel ?? "Working..." : props.label
|
|
9045
|
+
}
|
|
9046
|
+
);
|
|
9047
|
+
}
|
|
9048
|
+
function CodeForm(props) {
|
|
9049
|
+
const [code, setCode] = react.useState("");
|
|
9050
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9051
|
+
"form",
|
|
9052
|
+
{
|
|
9053
|
+
className: "pollar-recovery-row",
|
|
9054
|
+
onSubmit: (e) => {
|
|
9055
|
+
e.preventDefault();
|
|
9056
|
+
if (code.trim()) props.onSubmit(code);
|
|
9057
|
+
},
|
|
9058
|
+
children: [
|
|
9059
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9060
|
+
"input",
|
|
9061
|
+
{
|
|
9062
|
+
className: "pollar-recovery-input",
|
|
9063
|
+
value: code,
|
|
9064
|
+
onChange: (e) => setCode(e.target.value),
|
|
9065
|
+
placeholder: "Code from the email",
|
|
9066
|
+
autoComplete: "one-time-code",
|
|
9067
|
+
"aria-label": "Code from the email"
|
|
9068
|
+
}
|
|
9069
|
+
),
|
|
9070
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9071
|
+
Button,
|
|
9072
|
+
{
|
|
9073
|
+
busy: props.busy,
|
|
9074
|
+
id: props.id,
|
|
9075
|
+
label: props.label,
|
|
9076
|
+
kind: "primary",
|
|
9077
|
+
onClick: () => code.trim() && props.onSubmit(code)
|
|
9078
|
+
}
|
|
9079
|
+
)
|
|
9080
|
+
]
|
|
9081
|
+
}
|
|
9082
|
+
);
|
|
9083
|
+
}
|
|
9084
|
+
function RecoveryModalTemplate(p) {
|
|
9085
|
+
const cssVars = buildModalCssVars(p.theme, p.accentColor, p.styleOverrides);
|
|
9086
|
+
const [exportAccepted, setExportAccepted] = react.useState(false);
|
|
9087
|
+
const [risksAccepted, setRisksAccepted] = react.useState(false);
|
|
9088
|
+
const state = p.state;
|
|
9089
|
+
const locked = !!state && state.required && !state.configured;
|
|
9090
|
+
const risksPending = locked && p.selfCustodyRisks && !risksAccepted;
|
|
9091
|
+
const lastMethod = !!state && state.passkeys.length + (state.email.verified ? 1 : 0) + (state.devices.length >= 2 ? 1 : 0) <= 1;
|
|
9092
|
+
let body;
|
|
9093
|
+
if (!state) {
|
|
9094
|
+
body = /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: "Loading your wallet's recovery methods..." });
|
|
9095
|
+
} else if (!state.required) {
|
|
9096
|
+
body = /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: "This wallet doesn't need recovery methods: it isn't secured by your device." });
|
|
9097
|
+
} else if (!p.thisDeviceEnrolled && p.view !== "recover-email") {
|
|
9098
|
+
body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9099
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "status", children: "This device isn't part of your wallet yet. Get back in with one of your recovery methods, or ask a device that already has the wallet to add this one." }),
|
|
9100
|
+
state.passkeys.length > 0 && p.passkeyAvailable && /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "recover-passkey", label: "Use a passkey", kind: "primary", onClick: p.onRecoverWithPasskey }),
|
|
9101
|
+
state.email.verified && /* @__PURE__ */ jsxRuntime.jsx(
|
|
9102
|
+
Button,
|
|
9103
|
+
{
|
|
9104
|
+
busy: p.busy,
|
|
9105
|
+
id: "recover-send",
|
|
9106
|
+
label: `Email a code to ${state.email.address ?? "your email"}`,
|
|
9107
|
+
onClick: () => p.onView("recover-email")
|
|
9108
|
+
}
|
|
9109
|
+
),
|
|
9110
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "enroll", label: "Ask another device to add this one", onClick: p.onRequestEnrollment })
|
|
9111
|
+
] });
|
|
9112
|
+
} else if (risksPending && p.view === "overview") {
|
|
9113
|
+
body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9114
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: "This is a self-custody wallet: only your device can sign. If you lose this device and every recovery method, nobody can recover the wallet or its funds, not the app and not Pollar. Nobody can undo or freeze a transaction you sign." }),
|
|
9115
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9116
|
+
Button,
|
|
9117
|
+
{
|
|
9118
|
+
busy: p.busy,
|
|
9119
|
+
id: "accept-risks",
|
|
9120
|
+
label: "I understand and accept these risks",
|
|
9121
|
+
kind: "primary",
|
|
9122
|
+
onClick: () => setRisksAccepted(true)
|
|
9123
|
+
}
|
|
9124
|
+
)
|
|
9125
|
+
] });
|
|
9126
|
+
} else if (p.view === "recover-email") {
|
|
9127
|
+
body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9128
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-recovery-muted", children: [
|
|
9129
|
+
"We'll email a code to ",
|
|
9130
|
+
state.email.address,
|
|
9131
|
+
". The code goes straight to the wallet provider, encrypted on this device."
|
|
9132
|
+
] }),
|
|
9133
|
+
!p.codeSent ? /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "recover-send", label: "Send code", kind: "primary", onClick: p.onSendRecoveryCode }) : /* @__PURE__ */ jsxRuntime.jsx(CodeForm, { busy: p.busy, id: "recover-code", label: "Recover", onSubmit: p.onRecoverWithCode }),
|
|
9134
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
|
|
9135
|
+
] });
|
|
9136
|
+
} else if (p.view === "email") {
|
|
9137
|
+
body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9138
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: "Confirm an email you control. If you lose this device, a code sent there lets you add a new one. Anyone who controls this inbox could do the same, so use one that is well protected." }),
|
|
9139
|
+
!p.codeSent ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9140
|
+
"form",
|
|
9141
|
+
{
|
|
9142
|
+
className: "pollar-recovery-row",
|
|
9143
|
+
onSubmit: (e) => {
|
|
9144
|
+
e.preventDefault();
|
|
9145
|
+
p.onSendEmailCode();
|
|
9146
|
+
},
|
|
9147
|
+
children: [
|
|
9148
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9149
|
+
"input",
|
|
9150
|
+
{
|
|
9151
|
+
className: "pollar-recovery-input",
|
|
9152
|
+
type: "email",
|
|
9153
|
+
value: p.emailDraft,
|
|
9154
|
+
onChange: (e) => p.onEmailDraft(e.target.value),
|
|
9155
|
+
placeholder: "you@example.com",
|
|
9156
|
+
"aria-label": "Email"
|
|
9157
|
+
}
|
|
9158
|
+
),
|
|
9159
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9160
|
+
Button,
|
|
9161
|
+
{
|
|
9162
|
+
busy: p.busy,
|
|
9163
|
+
id: "email-send",
|
|
9164
|
+
label: "Send code",
|
|
9165
|
+
kind: "primary",
|
|
9166
|
+
onClick: p.onSendEmailCode,
|
|
9167
|
+
disabled: !p.emailDraft.trim()
|
|
9168
|
+
}
|
|
9169
|
+
)
|
|
9170
|
+
]
|
|
9171
|
+
}
|
|
9172
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(CodeForm, { busy: p.busy, id: "email-confirm", label: "Confirm", onSubmit: p.onConfirmEmail }),
|
|
9173
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
|
|
9174
|
+
] });
|
|
9175
|
+
} else if (p.view === "export") {
|
|
9176
|
+
body = p.phrase ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9177
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: "Anyone with these words controls every account in this wallet. Write them down offline and never share them." }),
|
|
9178
|
+
/* @__PURE__ */ jsxRuntime.jsx("pre", { className: "pollar-recovery-phrase", children: p.phrase }),
|
|
9179
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "hide", label: "Hide", kind: "primary", onClick: () => p.onView("overview") })
|
|
9180
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9181
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: "Your recovery phrase lets you take this wallet to another app. It is decrypted only on this device; the app and Pollar never see it." }),
|
|
9182
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-recovery-check", children: [
|
|
9183
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: exportAccepted, onChange: (e) => setExportAccepted(e.target.checked) }),
|
|
9184
|
+
" I understand that whoever sees the phrase controls the wallet"
|
|
9185
|
+
] }),
|
|
9186
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9187
|
+
Button,
|
|
9188
|
+
{
|
|
9189
|
+
busy: p.busy,
|
|
9190
|
+
id: "export",
|
|
9191
|
+
label: "Show recovery phrase",
|
|
9192
|
+
kind: "danger",
|
|
9193
|
+
onClick: p.onExport,
|
|
9194
|
+
disabled: !exportAccepted
|
|
9195
|
+
}
|
|
9196
|
+
),
|
|
9197
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
|
|
9198
|
+
] });
|
|
9199
|
+
} else {
|
|
9200
|
+
const enrollments = p.devices?.enrollments ?? [];
|
|
9201
|
+
body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9202
|
+
locked ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: "Your wallet is locked until you add a way to recover it. Without one, losing this device means losing the funds, and nobody can get them back, not the app and not Pollar. Add a passkey, a second device or your email." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-ok", role: "status", children: "Your wallet can be recovered if you lose this device." }),
|
|
9203
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
|
|
9204
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Passkeys" }),
|
|
9205
|
+
state.passkeys.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: "No passkey yet." }),
|
|
9206
|
+
state.passkeys.map((pk) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-item", children: [
|
|
9207
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: pk.name }),
|
|
9208
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9209
|
+
Button,
|
|
9210
|
+
{
|
|
9211
|
+
busy: p.busy,
|
|
9212
|
+
id: `passkey:${pk.authenticatorId}`,
|
|
9213
|
+
label: "Remove",
|
|
9214
|
+
kind: "danger",
|
|
9215
|
+
onClick: () => p.onRemovePasskey(pk.authenticatorId),
|
|
9216
|
+
disabled: lastMethod
|
|
9217
|
+
}
|
|
9218
|
+
)
|
|
9219
|
+
] }, pk.authenticatorId)),
|
|
9220
|
+
p.passkeyAvailable ? /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "passkey", label: "Add a passkey", onClick: p.onAddPasskey }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: "Passkeys aren't available on this device." })
|
|
9221
|
+
] }),
|
|
9222
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
|
|
9223
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Email" }),
|
|
9224
|
+
state.email.verified ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-item", children: [
|
|
9225
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: state.email.address }),
|
|
9226
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-recovery-tag", children: "Confirmed" })
|
|
9227
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "email", label: "Confirm an email", onClick: () => p.onView("email") })
|
|
9228
|
+
] }),
|
|
9229
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
|
|
9230
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Devices" }),
|
|
9231
|
+
(p.devices?.devices ?? []).map((d) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-item", children: [
|
|
9232
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: d.label ?? shortKey(d.publicKey) }),
|
|
9233
|
+
d.publicKey.toLowerCase() === p.thisDeviceKey && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-recovery-tag", children: "This device" })
|
|
9234
|
+
] }, d.id)),
|
|
9235
|
+
enrollments.map((e) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-item pollar-recovery-item-pending", children: [
|
|
9236
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
9237
|
+
e.label ?? "A device",
|
|
9238
|
+
" wants to join (",
|
|
9239
|
+
shortKey(e.publicKey),
|
|
9240
|
+
")"
|
|
9241
|
+
] }),
|
|
9242
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-recovery-actions", children: [
|
|
9243
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9244
|
+
Button,
|
|
9245
|
+
{
|
|
9246
|
+
busy: p.busy,
|
|
9247
|
+
id: `approve:${e.id}`,
|
|
9248
|
+
label: "Approve",
|
|
9249
|
+
kind: "primary",
|
|
9250
|
+
onClick: () => p.onApproveDevice(e.id)
|
|
9251
|
+
}
|
|
9252
|
+
),
|
|
9253
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: `reject:${e.id}`, label: "Reject", onClick: () => p.onRejectDevice(e.id) })
|
|
9254
|
+
] })
|
|
9255
|
+
] }, e.id)),
|
|
9256
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: 'To add another device, sign in there and choose "Ask another device to add this one"; it shows up here to approve. Only approve a device you are holding.' })
|
|
9257
|
+
] }),
|
|
9258
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
|
|
9259
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Recovery phrase" }),
|
|
9260
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "export", label: "Export recovery phrase", onClick: () => p.onView("export") })
|
|
9261
|
+
] })
|
|
9262
|
+
] });
|
|
9263
|
+
}
|
|
9264
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9265
|
+
"div",
|
|
9266
|
+
{
|
|
9267
|
+
className: "pollar-modal-card pollar-recovery-modal",
|
|
9268
|
+
"data-theme": p.theme,
|
|
9269
|
+
style: cssVars,
|
|
9270
|
+
onClick: (e) => e.stopPropagation(),
|
|
9271
|
+
children: [
|
|
9272
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-header", children: [
|
|
9273
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "pollar-modal-title", children: "Wallet recovery" }),
|
|
9274
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-modal-close", onClick: p.onClose, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon3, {}) }) })
|
|
9275
|
+
] }),
|
|
9276
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-body", children: [
|
|
9277
|
+
body,
|
|
9278
|
+
p.notice && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-notice", role: "status", children: p.notice }),
|
|
9279
|
+
p.error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-error", role: "alert", children: p.error })
|
|
9280
|
+
] }),
|
|
9281
|
+
/* @__PURE__ */ jsxRuntime.jsx(PollarModalFooter, {})
|
|
9282
|
+
]
|
|
9283
|
+
}
|
|
9284
|
+
);
|
|
9285
|
+
}
|
|
9286
|
+
function errorText2(err) {
|
|
9287
|
+
return core.transactionErrorMessage(core.isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null);
|
|
9288
|
+
}
|
|
9289
|
+
function RecoveryModal({ onClose }) {
|
|
9290
|
+
const { getClient, styles, refreshRecovery, appConfig } = usePollar();
|
|
9291
|
+
const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
|
|
9292
|
+
const recovery = getClient().cosign.recovery;
|
|
9293
|
+
const [state, setState] = react.useState(null);
|
|
9294
|
+
const [devices, setDevices] = react.useState(null);
|
|
9295
|
+
const [thisDeviceKey, setThisDeviceKey] = react.useState(null);
|
|
9296
|
+
const [view, setView] = react.useState("overview");
|
|
9297
|
+
const [busy, setBusy] = react.useState(null);
|
|
9298
|
+
const [error, setError] = react.useState(null);
|
|
9299
|
+
const [notice, setNotice] = react.useState(null);
|
|
9300
|
+
const [emailDraft, setEmailDraft] = react.useState("");
|
|
9301
|
+
const [codeSent, setCodeSent] = react.useState(false);
|
|
9302
|
+
const [phrase, setPhrase] = react.useState(null);
|
|
9303
|
+
const load = react.useCallback(async () => {
|
|
9304
|
+
const client = getClient();
|
|
9305
|
+
const [nextState, nextDevices, key] = await Promise.all([
|
|
9306
|
+
client.cosign.recovery.status(),
|
|
9307
|
+
client.cosign.devices(),
|
|
9308
|
+
client.cosign.publicKeyHex()
|
|
9309
|
+
]);
|
|
9310
|
+
setState(nextState);
|
|
9311
|
+
setDevices(nextDevices);
|
|
9312
|
+
setThisDeviceKey(key.toLowerCase());
|
|
9313
|
+
if (nextState.email.address) setEmailDraft((d) => d || nextState.email.address);
|
|
9314
|
+
refreshRecovery();
|
|
9315
|
+
}, [getClient, refreshRecovery]);
|
|
9316
|
+
react.useEffect(() => {
|
|
9317
|
+
load().catch((err) => setError(errorText2(err)));
|
|
9318
|
+
}, [load]);
|
|
9319
|
+
const onCloseRef = react.useRef(onClose);
|
|
9320
|
+
onCloseRef.current = onClose;
|
|
9321
|
+
react.useEffect(
|
|
9322
|
+
() => getClient().onAuthStateChange((authState) => {
|
|
9323
|
+
if (authState.step === "idle") onCloseRef.current();
|
|
9324
|
+
}),
|
|
9325
|
+
[getClient]
|
|
9326
|
+
);
|
|
9327
|
+
const run = react.useCallback(
|
|
9328
|
+
async (key, action, after) => {
|
|
9329
|
+
setBusy(key);
|
|
9330
|
+
setError(null);
|
|
9331
|
+
setNotice(null);
|
|
9332
|
+
try {
|
|
9333
|
+
await action();
|
|
9334
|
+
await load();
|
|
9335
|
+
if (after?.notice) setNotice(after.notice);
|
|
9336
|
+
if (after?.view) setView(after.view);
|
|
9337
|
+
} catch (err) {
|
|
9338
|
+
setError(errorText2(err));
|
|
9339
|
+
} finally {
|
|
9340
|
+
setBusy(null);
|
|
9341
|
+
}
|
|
9342
|
+
},
|
|
9343
|
+
[load]
|
|
9344
|
+
);
|
|
9345
|
+
const enrolled = !!thisDeviceKey && !!devices?.devices.some((d) => d.publicKey.toLowerCase() === thisDeviceKey);
|
|
9346
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-overlay", style: overlayStyle, onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9347
|
+
RecoveryModalTemplate,
|
|
9348
|
+
{
|
|
9349
|
+
theme,
|
|
9350
|
+
accentColor,
|
|
9351
|
+
styleOverrides,
|
|
9352
|
+
state,
|
|
9353
|
+
devices,
|
|
9354
|
+
thisDeviceKey,
|
|
9355
|
+
thisDeviceEnrolled: enrolled,
|
|
9356
|
+
passkeyAvailable: recovery.passkeyAvailable,
|
|
9357
|
+
selfCustodyRisks: appConfig.application?.walletCustodyMode === "SELF_CUSTODY" && devices?.custody === "SELF_CUSTODY",
|
|
9358
|
+
view,
|
|
9359
|
+
busy,
|
|
9360
|
+
error,
|
|
9361
|
+
notice,
|
|
9362
|
+
emailDraft,
|
|
9363
|
+
codeSent,
|
|
9364
|
+
phrase,
|
|
9365
|
+
onView: (next) => {
|
|
9366
|
+
setError(null);
|
|
9367
|
+
setNotice(null);
|
|
9368
|
+
setCodeSent(false);
|
|
9369
|
+
if (next !== "export") setPhrase(null);
|
|
9370
|
+
setView(next);
|
|
9371
|
+
},
|
|
9372
|
+
onEmailDraft: setEmailDraft,
|
|
9373
|
+
onAddPasskey: () => void run("passkey", () => recovery.addPasskey(), { notice: "Passkey added." }),
|
|
9374
|
+
onRemovePasskey: (id) => void run(`passkey:${id}`, () => recovery.removePasskey(id)),
|
|
9375
|
+
onSendEmailCode: () => void run("email-send", async () => {
|
|
9376
|
+
await recovery.startEmail(emailDraft.trim());
|
|
9377
|
+
setCodeSent(true);
|
|
9378
|
+
}),
|
|
9379
|
+
onConfirmEmail: (code) => void run("email-confirm", () => recovery.confirmEmail(code.trim()), { notice: "Email confirmed.", view: "overview" }),
|
|
9380
|
+
onApproveDevice: (id) => void run(`approve:${id}`, () => recovery.approveDevice(id), { notice: "Device added." }),
|
|
9381
|
+
onRejectDevice: (id) => void run(`reject:${id}`, () => recovery.rejectDevice(id)),
|
|
9382
|
+
onRequestEnrollment: () => void run("enroll", () => recovery.requestDeviceEnrollment(), {
|
|
9383
|
+
notice: "Request sent. Approve it from a device that already has this wallet within 15 minutes."
|
|
9384
|
+
}),
|
|
9385
|
+
onRecoverWithPasskey: () => void run("recover-passkey", () => recovery.recoverWithPasskey(), {
|
|
9386
|
+
notice: "This device is part of your wallet again."
|
|
9387
|
+
}),
|
|
9388
|
+
onSendRecoveryCode: () => void run("recover-send", async () => {
|
|
9389
|
+
await recovery.startEmailRecovery();
|
|
9390
|
+
setCodeSent(true);
|
|
9391
|
+
}),
|
|
9392
|
+
onRecoverWithCode: (code) => void run("recover-code", () => recovery.recoverWithEmailCode(code.trim()), {
|
|
9393
|
+
notice: "This device is part of your wallet again.",
|
|
9394
|
+
view: "overview"
|
|
9395
|
+
}),
|
|
9396
|
+
onExport: () => void run("export", async () => {
|
|
9397
|
+
setPhrase(await recovery.exportWallet());
|
|
9398
|
+
}),
|
|
9399
|
+
onClose
|
|
9400
|
+
}
|
|
9401
|
+
) });
|
|
9402
|
+
}
|
|
8430
9403
|
var OPERATION_LABELS2 = {
|
|
8431
9404
|
payment: "Send",
|
|
8432
9405
|
path_payment_strict_send: "Swap",
|
|
@@ -8453,7 +9426,7 @@ function detailRows2(confirmation) {
|
|
|
8453
9426
|
push("Network", confirmation.network);
|
|
8454
9427
|
return rows;
|
|
8455
9428
|
}
|
|
8456
|
-
function
|
|
9429
|
+
function CloseIcon4() {
|
|
8457
9430
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m3 3 8 8M11 3l-8 8", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) });
|
|
8458
9431
|
}
|
|
8459
9432
|
function ConfirmModal({ confirmation }) {
|
|
@@ -8477,7 +9450,7 @@ function ConfirmModal({ confirmation }) {
|
|
|
8477
9450
|
children: [
|
|
8478
9451
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-header", children: [
|
|
8479
9452
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "pollar-modal-title", children: "Confirm transaction" }),
|
|
8480
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-modal-close", onClick: confirmation.reject, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9453
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-modal-close", onClick: confirmation.reject, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon4, {}) }) })
|
|
8481
9454
|
] }),
|
|
8482
9455
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-detail", children: [
|
|
8483
9456
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-detail-title", children: labelOf2(confirmation) }),
|
|
@@ -8614,6 +9587,25 @@ function formatDate(iso) {
|
|
|
8614
9587
|
function truncateMiddle(value, head = 4, tail = 4) {
|
|
8615
9588
|
return value.length <= head + tail + 1 ? value : `${value.slice(0, head)}\u2026${value.slice(-tail)}`;
|
|
8616
9589
|
}
|
|
9590
|
+
function formatFee(fee) {
|
|
9591
|
+
if (fee.unit === "lamports") {
|
|
9592
|
+
try {
|
|
9593
|
+
return `${core.fromBaseUnits(fee.amount, 9)} SOL`;
|
|
9594
|
+
} catch {
|
|
9595
|
+
}
|
|
9596
|
+
}
|
|
9597
|
+
return `${fee.amount} ${fee.unit}`;
|
|
9598
|
+
}
|
|
9599
|
+
function formatAmount4(details) {
|
|
9600
|
+
const { amount, decimals, symbol } = details ?? {};
|
|
9601
|
+
if (typeof amount !== "string" || typeof decimals !== "number") return null;
|
|
9602
|
+
try {
|
|
9603
|
+
const value = core.fromBaseUnits(amount, decimals);
|
|
9604
|
+
return typeof symbol === "string" ? `${value} ${symbol}` : value;
|
|
9605
|
+
} catch {
|
|
9606
|
+
return null;
|
|
9607
|
+
}
|
|
9608
|
+
}
|
|
8617
9609
|
var STELLAR_ADDRESS = /\b[GC][A-Z2-7]{55}\b/g;
|
|
8618
9610
|
function AddressChip({ value, label }) {
|
|
8619
9611
|
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-hist-item-issuer", children: [
|
|
@@ -8751,15 +9743,17 @@ function TxHistoryModalTemplate({
|
|
|
8751
9743
|
] }),
|
|
8752
9744
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-hist-item-meta", children: [
|
|
8753
9745
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: record.operation }),
|
|
9746
|
+
record.chain !== "STELLAR" && formatAmount4(record.details) && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
9747
|
+
"\xB7 ",
|
|
9748
|
+
formatAmount4(record.details)
|
|
9749
|
+
] }),
|
|
8754
9750
|
typeof record.details?.sponsored === "boolean" && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
8755
9751
|
"\xB7 ",
|
|
8756
9752
|
record.details.sponsored ? "Sponsored" : "Self-paid"
|
|
8757
9753
|
] }),
|
|
8758
9754
|
record.fee && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
8759
9755
|
"\xB7 ",
|
|
8760
|
-
record.fee
|
|
8761
|
-
" ",
|
|
8762
|
-
record.fee.unit
|
|
9756
|
+
formatFee(record.fee)
|
|
8763
9757
|
] })
|
|
8764
9758
|
] }),
|
|
8765
9759
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-hist-item-footer", children: [
|
|
@@ -9070,6 +10064,48 @@ var browserPasskeySigner = async ({ credentialId, challenge }) => {
|
|
|
9070
10064
|
signature: response.signature
|
|
9071
10065
|
};
|
|
9072
10066
|
};
|
|
10067
|
+
var browserRecoveryPasskey = {
|
|
10068
|
+
async create({ challenge, userName }) {
|
|
10069
|
+
const rpId = window.location.hostname;
|
|
10070
|
+
const response = await browser.startRegistration({
|
|
10071
|
+
optionsJSON: {
|
|
10072
|
+
challenge,
|
|
10073
|
+
rp: { id: rpId, name: rpId },
|
|
10074
|
+
user: { id: randomUserId(), name: userName, displayName: userName },
|
|
10075
|
+
pubKeyCredParams: [
|
|
10076
|
+
{ type: "public-key", alg: -7 },
|
|
10077
|
+
{ type: "public-key", alg: -257 }
|
|
10078
|
+
],
|
|
10079
|
+
authenticatorSelection: { residentKey: "preferred", userVerification: "preferred" },
|
|
10080
|
+
attestation: "none",
|
|
10081
|
+
timeout: 6e4
|
|
10082
|
+
}
|
|
10083
|
+
});
|
|
10084
|
+
return {
|
|
10085
|
+
credentialId: response.id,
|
|
10086
|
+
clientDataJson: response.response.clientDataJSON,
|
|
10087
|
+
attestationObject: response.response.attestationObject,
|
|
10088
|
+
transports: response.response.transports ?? []
|
|
10089
|
+
};
|
|
10090
|
+
},
|
|
10091
|
+
async get({ challenge, allowCredentials }) {
|
|
10092
|
+
const rpId = window.location.hostname;
|
|
10093
|
+
const response = await browser.startAuthentication({
|
|
10094
|
+
optionsJSON: {
|
|
10095
|
+
challenge,
|
|
10096
|
+
rpId,
|
|
10097
|
+
allowCredentials: allowCredentials.map((id) => ({ id, type: "public-key" })),
|
|
10098
|
+
userVerification: "preferred"
|
|
10099
|
+
}
|
|
10100
|
+
});
|
|
10101
|
+
return {
|
|
10102
|
+
credentialId: response.id,
|
|
10103
|
+
authenticatorData: response.response.authenticatorData,
|
|
10104
|
+
clientDataJson: response.response.clientDataJSON,
|
|
10105
|
+
signature: response.response.signature
|
|
10106
|
+
};
|
|
10107
|
+
}
|
|
10108
|
+
};
|
|
9073
10109
|
var DEFAULT_APP_CONFIG = {
|
|
9074
10110
|
application: { name: "", network: "testnet", chains: [] },
|
|
9075
10111
|
styles: {}
|
|
@@ -9094,7 +10130,11 @@ function PollarProvider({
|
|
|
9094
10130
|
}) {
|
|
9095
10131
|
const [pollarClient] = react.useState(() => {
|
|
9096
10132
|
if (core.isPollarClient(client)) {
|
|
9097
|
-
client.setPasskeyDefaults({
|
|
10133
|
+
client.setPasskeyDefaults({
|
|
10134
|
+
passkey: browserPasskeyCeremony,
|
|
10135
|
+
passkeySign: browserPasskeySigner,
|
|
10136
|
+
recoveryPasskey: browserRecoveryPasskey
|
|
10137
|
+
});
|
|
9098
10138
|
return client;
|
|
9099
10139
|
}
|
|
9100
10140
|
const alreadyBuilt = clientByConfig.get(client);
|
|
@@ -9102,7 +10142,8 @@ function PollarProvider({
|
|
|
9102
10142
|
const built = new core.PollarClient({
|
|
9103
10143
|
...client,
|
|
9104
10144
|
passkey: client.passkey ?? browserPasskeyCeremony,
|
|
9105
|
-
passkeySign: client.passkeySign ?? browserPasskeySigner
|
|
10145
|
+
passkeySign: client.passkeySign ?? browserPasskeySigner,
|
|
10146
|
+
recoveryPasskey: client.recoveryPasskey ?? browserRecoveryPasskey
|
|
9106
10147
|
});
|
|
9107
10148
|
clientByConfig.set(client, built);
|
|
9108
10149
|
return built;
|
|
@@ -9257,6 +10298,7 @@ function PollarProvider({
|
|
|
9257
10298
|
const [receiveModalOpen, setReceiveModalOpen] = react.useState(false);
|
|
9258
10299
|
const [sessionsModalOpen, setSessionsModalOpen] = react.useState(false);
|
|
9259
10300
|
const [cosignModalOpen, setCosignModalOpen] = react.useState(false);
|
|
10301
|
+
const [recoveryModalOpen, setRecoveryModalOpen] = react.useState(false);
|
|
9260
10302
|
const cosignPendingCount = react.useRef(0);
|
|
9261
10303
|
react.useEffect(() => {
|
|
9262
10304
|
if (cosign.pending.length > cosignPendingCount.current) setCosignModalOpen(true);
|
|
@@ -9279,6 +10321,7 @@ function PollarProvider({
|
|
|
9279
10321
|
setReceiveModalOpen(false);
|
|
9280
10322
|
setSessionsModalOpen(false);
|
|
9281
10323
|
setCosignModalOpen(false);
|
|
10324
|
+
setRecoveryModalOpen(false);
|
|
9282
10325
|
setDistributionRulesModalOpen(false);
|
|
9283
10326
|
}),
|
|
9284
10327
|
[pollarClient]
|
|
@@ -9287,6 +10330,45 @@ function PollarProvider({
|
|
|
9287
10330
|
const walletProvisioning = sessionState?.wallet?.provisioning;
|
|
9288
10331
|
const hasSession = sessionState !== null;
|
|
9289
10332
|
const getClient = react.useCallback(() => pollarClient, [pollarClient]);
|
|
10333
|
+
const [recovery, setRecovery] = react.useState(null);
|
|
10334
|
+
const [recoveryTick, setRecoveryTick] = react.useState(0);
|
|
10335
|
+
const refreshRecovery = react.useCallback(() => setRecoveryTick((t) => t + 1), []);
|
|
10336
|
+
react.useEffect(() => {
|
|
10337
|
+
if (!hasSession) {
|
|
10338
|
+
setRecovery(null);
|
|
10339
|
+
return;
|
|
10340
|
+
}
|
|
10341
|
+
let cancelled = false;
|
|
10342
|
+
pollarClient.cosign.recovery.status().then((next) => {
|
|
10343
|
+
if (!cancelled) setRecovery(next);
|
|
10344
|
+
}).catch(() => {
|
|
10345
|
+
if (!cancelled) setRecovery(null);
|
|
10346
|
+
});
|
|
10347
|
+
return () => {
|
|
10348
|
+
cancelled = true;
|
|
10349
|
+
};
|
|
10350
|
+
}, [pollarClient, hasSession, walletAddress, recoveryTick]);
|
|
10351
|
+
const recoveryLocked = !!recovery && recovery.required && !recovery.configured;
|
|
10352
|
+
const [custodyMigration, setCustodyMigration] = react.useState(null);
|
|
10353
|
+
const [custodyMigrationTick, setCustodyMigrationTick] = react.useState(0);
|
|
10354
|
+
const refreshCustodyMigration = react.useCallback(() => setCustodyMigrationTick((t) => t + 1), []);
|
|
10355
|
+
const [custodyMigrationLater, setCustodyMigrationLater] = react.useState(false);
|
|
10356
|
+
react.useEffect(() => {
|
|
10357
|
+
if (!hasSession) {
|
|
10358
|
+
setCustodyMigration(null);
|
|
10359
|
+
setCustodyMigrationLater(false);
|
|
10360
|
+
return;
|
|
10361
|
+
}
|
|
10362
|
+
let cancelled = false;
|
|
10363
|
+
pollarClient.cosign.custodyMigration().then((next) => {
|
|
10364
|
+
if (!cancelled) setCustodyMigration(next);
|
|
10365
|
+
}).catch(() => {
|
|
10366
|
+
if (!cancelled) setCustodyMigration(null);
|
|
10367
|
+
});
|
|
10368
|
+
return () => {
|
|
10369
|
+
cancelled = true;
|
|
10370
|
+
};
|
|
10371
|
+
}, [pollarClient, hasSession, walletAddress, custodyMigrationTick]);
|
|
9290
10372
|
const refreshWalletBalance = react.useCallback(() => pollarClient.refreshBalance(), [pollarClient, walletAddress]);
|
|
9291
10373
|
const refreshAssets = react.useCallback(() => pollarClient.refreshAssets(), [pollarClient, walletAddress]);
|
|
9292
10374
|
const contextValue = react.useMemo(() => {
|
|
@@ -9297,7 +10379,7 @@ function PollarProvider({
|
|
|
9297
10379
|
wallets: pollarClient.getWallets(),
|
|
9298
10380
|
isAuthenticated: hasSession,
|
|
9299
10381
|
hasWallet: pollarClient.hasWallet(),
|
|
9300
|
-
createWallet: () => pollarClient.createWallet(),
|
|
10382
|
+
createWallet: (options) => pollarClient.createWallet(options),
|
|
9301
10383
|
verified,
|
|
9302
10384
|
// client
|
|
9303
10385
|
getClient,
|
|
@@ -9366,6 +10448,12 @@ function PollarProvider({
|
|
|
9366
10448
|
// cosign
|
|
9367
10449
|
cosign,
|
|
9368
10450
|
openCosignModal: () => setCosignModalOpen(true),
|
|
10451
|
+
recovery,
|
|
10452
|
+
recoveryLocked,
|
|
10453
|
+
refreshRecovery,
|
|
10454
|
+
openRecoveryModal: () => setRecoveryModalOpen(true),
|
|
10455
|
+
custodyMigration,
|
|
10456
|
+
refreshCustodyMigration,
|
|
9369
10457
|
confirmations,
|
|
9370
10458
|
// distribution
|
|
9371
10459
|
openDistributionRulesModal: () => setDistributionRulesModalOpen(true),
|
|
@@ -9389,6 +10477,11 @@ function PollarProvider({
|
|
|
9389
10477
|
}, [
|
|
9390
10478
|
walletAddress,
|
|
9391
10479
|
walletProvisioning,
|
|
10480
|
+
recovery,
|
|
10481
|
+
recoveryLocked,
|
|
10482
|
+
refreshRecovery,
|
|
10483
|
+
custodyMigration,
|
|
10484
|
+
refreshCustodyMigration,
|
|
9392
10485
|
hasSession,
|
|
9393
10486
|
verified,
|
|
9394
10487
|
pollarClient,
|
|
@@ -9446,6 +10539,23 @@ function PollarProvider({
|
|
|
9446
10539
|
receiveModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setReceiveModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(ReceiveModal, { onClose: () => setReceiveModalOpen(false) }) }),
|
|
9447
10540
|
sessionsModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setSessionsModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(SessionsModal, { onClose: () => setSessionsModalOpen(false) }) }),
|
|
9448
10541
|
cosignModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setCosignModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(CosignModal, { onClose: () => setCosignModalOpen(false) }) }),
|
|
10542
|
+
custodyMigration?.required && !custodyMigrationLater && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: refreshCustodyMigration, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
10543
|
+
CustodyMigrationModal,
|
|
10544
|
+
{
|
|
10545
|
+
state: custodyMigration,
|
|
10546
|
+
onMoved: () => {
|
|
10547
|
+
refreshCustodyMigration();
|
|
10548
|
+
refreshRecovery();
|
|
10549
|
+
setRecoveryModalOpen(true);
|
|
10550
|
+
},
|
|
10551
|
+
onLater: () => {
|
|
10552
|
+
setCustodyMigrationLater(true);
|
|
10553
|
+
refreshCustodyMigration();
|
|
10554
|
+
refreshRecovery();
|
|
10555
|
+
}
|
|
10556
|
+
}
|
|
10557
|
+
) }),
|
|
10558
|
+
recoveryModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setRecoveryModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(RecoveryModal, { onClose: () => setRecoveryModalOpen(false) }) }),
|
|
9449
10559
|
firstConfirmation && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: firstConfirmation.reject, children: /* @__PURE__ */ jsxRuntime.jsx(ConfirmModal, { confirmation: firstConfirmation }, firstConfirmation.id) }),
|
|
9450
10560
|
distributionRulesModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setDistributionRulesModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(DistributionRulesModal, { onClose: () => setDistributionRulesModalOpen(false) }) })
|
|
9451
10561
|
] });
|
|
@@ -9496,6 +10606,8 @@ function WalletButtonTemplate({
|
|
|
9496
10606
|
isInProgress,
|
|
9497
10607
|
showCreateAccount,
|
|
9498
10608
|
notReadyReason,
|
|
10609
|
+
recoveryLocked,
|
|
10610
|
+
onRecovery,
|
|
9499
10611
|
creatingAccount,
|
|
9500
10612
|
onToggleOpen,
|
|
9501
10613
|
onCreateAccount,
|
|
@@ -9513,8 +10625,11 @@ function WalletButtonTemplate({
|
|
|
9513
10625
|
signedIn,
|
|
9514
10626
|
creatingWallet,
|
|
9515
10627
|
createWalletError,
|
|
10628
|
+
custodyChoice,
|
|
9516
10629
|
onCreateWallet
|
|
9517
10630
|
}) {
|
|
10631
|
+
const [selfCustodyNotice, setSelfCustodyNotice] = react.useState(false);
|
|
10632
|
+
const [risksAccepted, setRisksAccepted] = react.useState(false);
|
|
9518
10633
|
if (!walletAddress && signedIn) {
|
|
9519
10634
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-wallet-wrapper", ref: wrapperRef, children: [
|
|
9520
10635
|
/* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-btn", style: { backgroundColor: accentColor }, onClick: onToggleOpen, children: [
|
|
@@ -9538,19 +10653,84 @@ function WalletButtonTemplate({
|
|
|
9538
10653
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "status", children: createWalletError }),
|
|
9539
10654
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
|
|
9540
10655
|
] }),
|
|
9541
|
-
onCreateWallet && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10656
|
+
onCreateWallet && !custodyChoice && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9542
10657
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9543
10658
|
"button",
|
|
9544
10659
|
{
|
|
9545
10660
|
className: "pollar-wallet-dropdown-item",
|
|
9546
10661
|
style: { color: itemColor },
|
|
9547
|
-
onClick: onCreateWallet,
|
|
10662
|
+
onClick: () => onCreateWallet(),
|
|
9548
10663
|
disabled: creatingWallet,
|
|
9549
10664
|
children: creatingWallet ? "Creating wallet\u2026" : "Create wallet"
|
|
9550
10665
|
}
|
|
9551
10666
|
),
|
|
9552
10667
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
|
|
9553
10668
|
] }),
|
|
10669
|
+
onCreateWallet && custodyChoice && !selfCustodyNotice && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10670
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10671
|
+
"button",
|
|
10672
|
+
{
|
|
10673
|
+
className: "pollar-wallet-dropdown-item",
|
|
10674
|
+
style: { color: itemColor },
|
|
10675
|
+
onClick: () => onCreateWallet("COSIGNED"),
|
|
10676
|
+
disabled: creatingWallet,
|
|
10677
|
+
children: creatingWallet ? "Creating wallet\u2026" : "Create co-signed wallet"
|
|
10678
|
+
}
|
|
10679
|
+
),
|
|
10680
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, children: "You and the app approve every signature together. Your wallet can be recovered with help." }),
|
|
10681
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10682
|
+
"button",
|
|
10683
|
+
{
|
|
10684
|
+
className: "pollar-wallet-dropdown-item",
|
|
10685
|
+
style: { color: itemColor },
|
|
10686
|
+
onClick: () => setSelfCustodyNotice(true),
|
|
10687
|
+
disabled: creatingWallet,
|
|
10688
|
+
children: "Create self-custody wallet"
|
|
10689
|
+
}
|
|
10690
|
+
),
|
|
10691
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, children: "Only your device signs. Nobody else can move or recover your funds." }),
|
|
10692
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
|
|
10693
|
+
] }),
|
|
10694
|
+
onCreateWallet && custodyChoice && selfCustodyNotice && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10695
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "alert", children: "In a self-custody wallet only your device can sign. If you lose this device and every recovery method, nobody can recover the wallet or its funds, not the app and not Pollar. Nobody can undo or freeze a transaction you sign." }),
|
|
10696
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, children: [
|
|
10697
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10698
|
+
"input",
|
|
10699
|
+
{
|
|
10700
|
+
type: "checkbox",
|
|
10701
|
+
checked: risksAccepted,
|
|
10702
|
+
onChange: (e) => setRisksAccepted(e.target.checked),
|
|
10703
|
+
disabled: creatingWallet
|
|
10704
|
+
}
|
|
10705
|
+
),
|
|
10706
|
+
" ",
|
|
10707
|
+
"I understand and accept these risks"
|
|
10708
|
+
] }),
|
|
10709
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10710
|
+
"button",
|
|
10711
|
+
{
|
|
10712
|
+
className: "pollar-wallet-dropdown-item",
|
|
10713
|
+
style: { color: itemColor },
|
|
10714
|
+
onClick: () => onCreateWallet("SELF_CUSTODY"),
|
|
10715
|
+
disabled: creatingWallet || !risksAccepted,
|
|
10716
|
+
children: creatingWallet ? "Creating wallet\u2026" : "Create self-custody wallet"
|
|
10717
|
+
}
|
|
10718
|
+
),
|
|
10719
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10720
|
+
"button",
|
|
10721
|
+
{
|
|
10722
|
+
className: "pollar-wallet-dropdown-item",
|
|
10723
|
+
style: { color: itemColor },
|
|
10724
|
+
onClick: () => {
|
|
10725
|
+
setSelfCustodyNotice(false);
|
|
10726
|
+
setRisksAccepted(false);
|
|
10727
|
+
},
|
|
10728
|
+
disabled: creatingWallet,
|
|
10729
|
+
children: "Back"
|
|
10730
|
+
}
|
|
10731
|
+
),
|
|
10732
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
|
|
10733
|
+
] }),
|
|
9554
10734
|
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onSessions, children: "Active sessions" }),
|
|
9555
10735
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" }),
|
|
9556
10736
|
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-wallet-dropdown-item pollar-danger", onClick: onLogout, children: "Log out" })
|
|
@@ -9606,6 +10786,10 @@ function WalletButtonTemplate({
|
|
|
9606
10786
|
}
|
|
9607
10787
|
),
|
|
9608
10788
|
open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-wallet-dropdown", style: { backgroundColor: dropdownBg, borderColor: dropdownBorder }, children: [
|
|
10789
|
+
recoveryLocked && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10790
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice pollar-danger", role: "alert", children: "Locked: add a way to recover this wallet before sending or receiving. If you lose this device without one, the funds are lost." }),
|
|
10791
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
|
|
10792
|
+
] }),
|
|
9609
10793
|
notReadyReason && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9610
10794
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "status", children: notReadyReason }),
|
|
9611
10795
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
|
|
@@ -9838,6 +11022,31 @@ function WalletButtonTemplate({
|
|
|
9838
11022
|
),
|
|
9839
11023
|
"Active sessions"
|
|
9840
11024
|
] }),
|
|
11025
|
+
onRecovery && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
11026
|
+
"button",
|
|
11027
|
+
{
|
|
11028
|
+
className: `pollar-wallet-dropdown-item${recoveryLocked ? " pollar-danger" : ""}`,
|
|
11029
|
+
style: recoveryLocked ? void 0 : { color: itemColor },
|
|
11030
|
+
onClick: onRecovery,
|
|
11031
|
+
children: [
|
|
11032
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11033
|
+
"svg",
|
|
11034
|
+
{
|
|
11035
|
+
width: "14",
|
|
11036
|
+
height: "14",
|
|
11037
|
+
viewBox: "0 0 24 24",
|
|
11038
|
+
fill: "none",
|
|
11039
|
+
stroke: "currentColor",
|
|
11040
|
+
strokeWidth: "2",
|
|
11041
|
+
strokeLinecap: "round",
|
|
11042
|
+
strokeLinejoin: "round",
|
|
11043
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2 4 5v6c0 5 3.4 9.3 8 11 4.6-1.7 8-6 8-11V5l-8-3z" })
|
|
11044
|
+
}
|
|
11045
|
+
),
|
|
11046
|
+
recoveryLocked ? "Set up wallet recovery" : "Wallet recovery"
|
|
11047
|
+
]
|
|
11048
|
+
}
|
|
11049
|
+
),
|
|
9841
11050
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" }),
|
|
9842
11051
|
/* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item pollar-danger", onClick: onLogout, children: [
|
|
9843
11052
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -9880,7 +11089,10 @@ function WalletButton() {
|
|
|
9880
11089
|
tx: transaction,
|
|
9881
11090
|
isAuthenticated,
|
|
9882
11091
|
createWallet,
|
|
9883
|
-
appConfig
|
|
11092
|
+
appConfig,
|
|
11093
|
+
recovery,
|
|
11094
|
+
recoveryLocked,
|
|
11095
|
+
openRecoveryModal
|
|
9884
11096
|
} = usePollar();
|
|
9885
11097
|
const { primaryChain, primaryAddress } = useChains();
|
|
9886
11098
|
const walletAddress = primaryAddress || (wallet?.address ?? "");
|
|
@@ -9943,12 +11155,12 @@ function WalletButton() {
|
|
|
9943
11155
|
setCreating(false);
|
|
9944
11156
|
}
|
|
9945
11157
|
}
|
|
9946
|
-
async function handleCreateWallet() {
|
|
11158
|
+
async function handleCreateWallet(custody) {
|
|
9947
11159
|
if (creatingWallet) return;
|
|
9948
11160
|
setCreatingWallet(true);
|
|
9949
11161
|
setCreateWalletError(null);
|
|
9950
11162
|
try {
|
|
9951
|
-
await createWallet();
|
|
11163
|
+
await createWallet(custody ? { custody } : void 0);
|
|
9952
11164
|
setOpen(false);
|
|
9953
11165
|
} catch (err) {
|
|
9954
11166
|
setCreateWalletError(
|
|
@@ -9978,6 +11190,10 @@ function WalletButton() {
|
|
|
9978
11190
|
setOpen(false);
|
|
9979
11191
|
openSessionsModal();
|
|
9980
11192
|
}
|
|
11193
|
+
function handleRecovery() {
|
|
11194
|
+
setOpen(false);
|
|
11195
|
+
openRecoveryModal();
|
|
11196
|
+
}
|
|
9981
11197
|
function handleKyc() {
|
|
9982
11198
|
setOpen(false);
|
|
9983
11199
|
openKycModal();
|
|
@@ -10006,6 +11222,7 @@ function WalletButton() {
|
|
|
10006
11222
|
signedIn: isAuthenticated,
|
|
10007
11223
|
creatingWallet,
|
|
10008
11224
|
createWalletError,
|
|
11225
|
+
custodyChoice: appConfig.application?.walletCustodyMode === "USER_CHOICE",
|
|
10009
11226
|
...appConfig.application?.walletProvisioning === "NONE" ? {} : { onCreateWallet: handleCreateWallet },
|
|
10010
11227
|
notReadyReason,
|
|
10011
11228
|
creatingAccount: creating,
|
|
@@ -10017,6 +11234,8 @@ function WalletButton() {
|
|
|
10017
11234
|
onSend: handleSend,
|
|
10018
11235
|
onReceive: handleReceive,
|
|
10019
11236
|
onSessions: handleSessions,
|
|
11237
|
+
recoveryLocked,
|
|
11238
|
+
...recovery?.required ? { onRecovery: handleRecovery } : {},
|
|
10020
11239
|
onKyc: handleKyc,
|
|
10021
11240
|
onRamp: handleRamp,
|
|
10022
11241
|
onDistributionRules: handleDistributionRules,
|
|
@@ -10030,6 +11249,8 @@ exports.CardModal = CardModal;
|
|
|
10030
11249
|
exports.ChainSelect = ChainSelect;
|
|
10031
11250
|
exports.CosignModal = CosignModal;
|
|
10032
11251
|
exports.CosignModalTemplate = CosignModalTemplate;
|
|
11252
|
+
exports.CustodyMigrationModal = CustodyMigrationModal;
|
|
11253
|
+
exports.CustodyMigrationModalTemplate = CustodyMigrationModalTemplate;
|
|
10033
11254
|
exports.DistributionRulesModal = DistributionRulesModal;
|
|
10034
11255
|
exports.DistributionRulesModalTemplate = DistributionRulesModalTemplate;
|
|
10035
11256
|
exports.EarnModal = EarnModal;
|
|
@@ -10043,8 +11264,11 @@ exports.PollarProvider = PollarProvider;
|
|
|
10043
11264
|
exports.ProviderRegistrationModal = ProviderRegistrationModal;
|
|
10044
11265
|
exports.RampWidget = RampWidget;
|
|
10045
11266
|
exports.RampWidgetTemplate = RampWidgetTemplate;
|
|
11267
|
+
exports.RampWorkflow = RampWorkflow;
|
|
10046
11268
|
exports.ReceiveModal = ReceiveModal;
|
|
10047
11269
|
exports.ReceiveModalTemplate = ReceiveModalTemplate;
|
|
11270
|
+
exports.RecoveryModal = RecoveryModal;
|
|
11271
|
+
exports.RecoveryModalTemplate = RecoveryModalTemplate;
|
|
10048
11272
|
exports.RegistryCheckModal = RegistryCheckModal;
|
|
10049
11273
|
exports.RequirementFormModal = RequirementFormModal;
|
|
10050
11274
|
exports.RouteDisplay = RouteDisplay;
|
|
@@ -10064,6 +11288,7 @@ exports.WalletButtonTemplate = WalletButtonTemplate;
|
|
|
10064
11288
|
exports.addressForChain = addressForChain;
|
|
10065
11289
|
exports.browserPasskeyCeremony = browserPasskeyCeremony;
|
|
10066
11290
|
exports.browserPasskeySigner = browserPasskeySigner;
|
|
11291
|
+
exports.browserRecoveryPasskey = browserRecoveryPasskey;
|
|
10067
11292
|
exports.buildModalCssVars = buildModalCssVars;
|
|
10068
11293
|
exports.chainsOf = chainsOf;
|
|
10069
11294
|
exports.createPollarAdapterHook = createPollarAdapterHook;
|