@rhinestone/deposit-modal 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -29
- package/dist/{chunk-4BLOPOVO.mjs → DepositModalReown-6KUAO5ZC.mjs} +56 -4
- package/dist/{chunk-5E67UM3V.cjs → DepositModalReown-XC5RIU7Z.cjs} +62 -10
- package/dist/{caip-C_ZYFIGa.d.cts → caip-523Zrc3r.d.cts} +1 -3
- package/dist/{caip-C_ZYFIGa.d.ts → caip-523Zrc3r.d.ts} +1 -3
- package/dist/{chunk-46BRQZ2P.cjs → chunk-22RV33LK.cjs} +2148 -1467
- package/dist/chunk-4WB2FHGC.mjs +865 -0
- package/dist/chunk-A223BLVC.mjs +801 -0
- package/dist/{chunk-3WIHTUQ6.cjs → chunk-DIX7M3SP.cjs} +232 -601
- package/dist/{chunk-XK5RH4FZ.mjs → chunk-E4G5BOZP.mjs} +167 -536
- package/dist/chunk-H577RJN5.cjs +801 -0
- package/dist/{chunk-RARNNVZO.mjs → chunk-ILT2PITN.mjs} +60 -3
- package/dist/{chunk-KGZU3BL2.mjs → chunk-KKHLNFSA.mjs} +2397 -1716
- package/dist/chunk-LGVJWVSS.mjs +81 -0
- package/dist/chunk-NEXUCY4B.cjs +11 -0
- package/dist/chunk-NMIMRASD.cjs +1785 -0
- package/dist/chunk-OSI2CFGL.mjs +11 -0
- package/dist/chunk-PAGMU4RQ.cjs +865 -0
- package/dist/chunk-T7LGQFP3.cjs +81 -0
- package/dist/chunk-VN4BLZYC.cjs +2273 -0
- package/dist/chunk-VOGZAZB2.mjs +2273 -0
- package/dist/chunk-WYXBY2MQ.mjs +1785 -0
- package/dist/{chunk-ORKEBFMR.cjs → chunk-YEBXUUH3.cjs} +63 -6
- package/dist/claim.cjs +10 -0
- package/dist/claim.d.cts +8 -0
- package/dist/claim.d.ts +8 -0
- package/dist/claim.mjs +10 -0
- package/dist/constants.cjs +3 -3
- package/dist/constants.d.cts +4 -2
- package/dist/constants.d.ts +4 -2
- package/dist/constants.mjs +5 -5
- package/dist/deposit.cjs +8 -6
- package/dist/deposit.d.cts +2 -2
- package/dist/deposit.d.ts +2 -2
- package/dist/deposit.mjs +7 -5
- package/dist/index.cjs +22 -12
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.mjs +21 -11
- package/dist/polymarket.cjs +5 -6
- package/dist/polymarket.mjs +2 -3
- package/dist/server.cjs +96 -0
- package/dist/server.d.cts +65 -0
- package/dist/server.d.ts +65 -0
- package/dist/server.mjs +96 -0
- package/dist/styles.css +149 -15
- package/dist/types-C0SGf-21.d.cts +606 -0
- package/dist/types-C0SGf-21.d.ts +606 -0
- package/dist/withdraw.cjs +6 -5
- package/dist/withdraw.d.cts +3 -6
- package/dist/withdraw.d.ts +3 -6
- package/dist/withdraw.mjs +5 -4
- package/package.json +53 -6
- package/dist/DepositModalReown-A2W6R27Q.cjs +0 -63
- package/dist/DepositModalReown-IB5WUXVT.mjs +0 -63
- package/dist/WithdrawModalReown-A4S4BJHG.mjs +0 -46
- package/dist/WithdrawModalReown-KUWNNA2X.cjs +0 -46
- package/dist/chunk-2O2FJYRY.mjs +0 -800
- package/dist/chunk-DSXMWQYB.cjs +0 -4542
- package/dist/chunk-P5T4XSN2.cjs +0 -800
- package/dist/chunk-RH6XQHMA.mjs +0 -295
- package/dist/chunk-TUSEN26M.mjs +0 -4542
- package/dist/chunk-UNVVUULP.cjs +0 -295
- package/dist/types--LQWg_4W.d.cts +0 -468
- package/dist/types--LQWg_4W.d.ts +0 -468
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/server.ts
|
|
2
|
+
var DEFAULT_PROCESSOR_URL = "https://v1.orchestrator.rhinestone.dev/deposit-processor";
|
|
3
|
+
function jsonError(message, status) {
|
|
4
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
5
|
+
status,
|
|
6
|
+
headers: { "Content-Type": "application/json" }
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
function parseBody(value) {
|
|
10
|
+
if (typeof value !== "object" || value === null) return null;
|
|
11
|
+
const record = value;
|
|
12
|
+
const fields = ["chain", "txHash", "account", "token", "destination"];
|
|
13
|
+
for (const field of fields) {
|
|
14
|
+
if (typeof record[field] !== "string" || record[field] === "") return null;
|
|
15
|
+
}
|
|
16
|
+
return Object.fromEntries(
|
|
17
|
+
fields.map((field) => [field, record[field]])
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
function sameIdentifier(a, b) {
|
|
21
|
+
if (a === b) return true;
|
|
22
|
+
const isHex = (value) => /^0x[0-9a-fA-F]+$/.test(value);
|
|
23
|
+
return isHex(a) && isHex(b) && a.toLowerCase() === b.toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
function createRefundHandler(options) {
|
|
26
|
+
const processorUrl = (_nullishCoalesce(options.processorUrl, () => ( DEFAULT_PROCESSOR_URL))).replace(
|
|
27
|
+
/\/$/,
|
|
28
|
+
""
|
|
29
|
+
);
|
|
30
|
+
const headers = {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
"x-api-key": options.apiKey
|
|
33
|
+
};
|
|
34
|
+
return async function handleRefund(request) {
|
|
35
|
+
const body = parseBody(await request.json().catch(() => null));
|
|
36
|
+
if (!body) {
|
|
37
|
+
return jsonError("Malformed refund request", 400);
|
|
38
|
+
}
|
|
39
|
+
const authorization = await options.authorize(request, body);
|
|
40
|
+
if (!authorization) {
|
|
41
|
+
return jsonError("Unauthorized", 401);
|
|
42
|
+
}
|
|
43
|
+
const query = new URLSearchParams({
|
|
44
|
+
recipient: authorization.depositRecipient,
|
|
45
|
+
chain: body.chain,
|
|
46
|
+
txHash: body.txHash,
|
|
47
|
+
includeSpam: "true"
|
|
48
|
+
});
|
|
49
|
+
const lookup = await fetch(`${processorUrl}/deposits?${query}`, { headers });
|
|
50
|
+
if (!lookup.ok) {
|
|
51
|
+
return jsonError("Could not verify the deposit", 502);
|
|
52
|
+
}
|
|
53
|
+
const { deposits = [] } = await lookup.json();
|
|
54
|
+
const owned = deposits.some(
|
|
55
|
+
(deposit) => deposit.depositAddress !== void 0 && deposit.token !== void 0 && sameIdentifier(deposit.depositAddress, body.account) && sameIdentifier(deposit.token, body.token)
|
|
56
|
+
);
|
|
57
|
+
if (!owned) {
|
|
58
|
+
return jsonError("Forbidden", 403);
|
|
59
|
+
}
|
|
60
|
+
const destination = _nullishCoalesce(authorization.refundDestination, () => ( body.destination));
|
|
61
|
+
const upstream = await fetch(`${processorUrl}/deposits/refund`, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers,
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
chain: body.chain,
|
|
66
|
+
txHash: body.txHash,
|
|
67
|
+
account: body.account,
|
|
68
|
+
token: body.token,
|
|
69
|
+
recipient: destination
|
|
70
|
+
})
|
|
71
|
+
});
|
|
72
|
+
const text = await upstream.text();
|
|
73
|
+
if (!upstream.ok) {
|
|
74
|
+
return new Response(text, {
|
|
75
|
+
status: upstream.status,
|
|
76
|
+
headers: { "Content-Type": "application/json" }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
let body_;
|
|
80
|
+
try {
|
|
81
|
+
body_ = JSON.parse(text);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return new Response(text, {
|
|
84
|
+
status: upstream.status,
|
|
85
|
+
headers: { "Content-Type": "application/json" }
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return new Response(
|
|
89
|
+
JSON.stringify({ ...body_, destination }),
|
|
90
|
+
{ status: upstream.status, headers: { "Content-Type": "application/json" } }
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
exports.createRefundHandler = createRefundHandler;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side helper for the claim flow. Import from
|
|
3
|
+
* `@rhinestone/deposit-modal/server` — this module talks to the processor with
|
|
4
|
+
* your API key and must never reach the browser.
|
|
5
|
+
*
|
|
6
|
+
* Deposit accounts are service-managed, so no user wallet can sign for a
|
|
7
|
+
* refund, and an exchange-funded deposit has no signable sender either.
|
|
8
|
+
* Whether a deposit belongs to a given user is something only your app knows,
|
|
9
|
+
* so that one decision is yours (`authorize`) and everything else is here.
|
|
10
|
+
*/
|
|
11
|
+
/** Body the ClaimModal POSTs to your endpoint. Never trusted on its own. */
|
|
12
|
+
interface RefundRequestBody {
|
|
13
|
+
chain: string;
|
|
14
|
+
txHash: string;
|
|
15
|
+
account: string;
|
|
16
|
+
token: string;
|
|
17
|
+
destination: string;
|
|
18
|
+
}
|
|
19
|
+
interface RefundAuthorization {
|
|
20
|
+
/**
|
|
21
|
+
* The deposit `recipient` this user owns — the same value your app passes
|
|
22
|
+
* `<DepositModal>`. Used *only* to prove the deposit belongs to them: the
|
|
23
|
+
* handler lists deposits settling to it and rejects a txHash that isn't
|
|
24
|
+
* among them.
|
|
25
|
+
*
|
|
26
|
+
* Not where the money goes. One recipient covers every deposit account the
|
|
27
|
+
* user has, since the account salt includes the target chain and token.
|
|
28
|
+
*/
|
|
29
|
+
depositRecipient: string;
|
|
30
|
+
/**
|
|
31
|
+
* Where the refund is paid. Set this to override whatever the browser asked
|
|
32
|
+
* for, taking the choice away from the page. Omit it to honour the address
|
|
33
|
+
* the user typed.
|
|
34
|
+
*/
|
|
35
|
+
refundDestination?: string;
|
|
36
|
+
}
|
|
37
|
+
interface CreateRefundHandlerOptions {
|
|
38
|
+
/** Write-scoped Rhinestone API key. Server-side only. */
|
|
39
|
+
apiKey: string;
|
|
40
|
+
/**
|
|
41
|
+
* Authenticate the caller and return the recipient they own, or `null` to
|
|
42
|
+
* reject with 401. This is the only part we can't write for you.
|
|
43
|
+
*/
|
|
44
|
+
authorize: (request: Request, refund: RefundRequestBody) => Promise<RefundAuthorization | null> | RefundAuthorization | null;
|
|
45
|
+
/** Defaults to the production processor. */
|
|
46
|
+
processorUrl?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Builds a `(Request) => Promise<Response>` you can mount in any fetch-based
|
|
50
|
+
* runtime (Next.js route handlers, Hono, Bun.serve, Cloudflare Workers).
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* // app/api/refund/route.ts
|
|
54
|
+
* export const POST = createRefundHandler({
|
|
55
|
+
* apiKey: process.env.RHINESTONE_API_KEY!,
|
|
56
|
+
* async authorize(request) {
|
|
57
|
+
* const session = await getSession(request);
|
|
58
|
+
* return session ? { depositRecipient: session.depositRecipient } : null;
|
|
59
|
+
* },
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function createRefundHandler(options: CreateRefundHandlerOptions): (request: Request) => Promise<Response>;
|
|
64
|
+
|
|
65
|
+
export { type CreateRefundHandlerOptions, type RefundAuthorization, createRefundHandler };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side helper for the claim flow. Import from
|
|
3
|
+
* `@rhinestone/deposit-modal/server` — this module talks to the processor with
|
|
4
|
+
* your API key and must never reach the browser.
|
|
5
|
+
*
|
|
6
|
+
* Deposit accounts are service-managed, so no user wallet can sign for a
|
|
7
|
+
* refund, and an exchange-funded deposit has no signable sender either.
|
|
8
|
+
* Whether a deposit belongs to a given user is something only your app knows,
|
|
9
|
+
* so that one decision is yours (`authorize`) and everything else is here.
|
|
10
|
+
*/
|
|
11
|
+
/** Body the ClaimModal POSTs to your endpoint. Never trusted on its own. */
|
|
12
|
+
interface RefundRequestBody {
|
|
13
|
+
chain: string;
|
|
14
|
+
txHash: string;
|
|
15
|
+
account: string;
|
|
16
|
+
token: string;
|
|
17
|
+
destination: string;
|
|
18
|
+
}
|
|
19
|
+
interface RefundAuthorization {
|
|
20
|
+
/**
|
|
21
|
+
* The deposit `recipient` this user owns — the same value your app passes
|
|
22
|
+
* `<DepositModal>`. Used *only* to prove the deposit belongs to them: the
|
|
23
|
+
* handler lists deposits settling to it and rejects a txHash that isn't
|
|
24
|
+
* among them.
|
|
25
|
+
*
|
|
26
|
+
* Not where the money goes. One recipient covers every deposit account the
|
|
27
|
+
* user has, since the account salt includes the target chain and token.
|
|
28
|
+
*/
|
|
29
|
+
depositRecipient: string;
|
|
30
|
+
/**
|
|
31
|
+
* Where the refund is paid. Set this to override whatever the browser asked
|
|
32
|
+
* for, taking the choice away from the page. Omit it to honour the address
|
|
33
|
+
* the user typed.
|
|
34
|
+
*/
|
|
35
|
+
refundDestination?: string;
|
|
36
|
+
}
|
|
37
|
+
interface CreateRefundHandlerOptions {
|
|
38
|
+
/** Write-scoped Rhinestone API key. Server-side only. */
|
|
39
|
+
apiKey: string;
|
|
40
|
+
/**
|
|
41
|
+
* Authenticate the caller and return the recipient they own, or `null` to
|
|
42
|
+
* reject with 401. This is the only part we can't write for you.
|
|
43
|
+
*/
|
|
44
|
+
authorize: (request: Request, refund: RefundRequestBody) => Promise<RefundAuthorization | null> | RefundAuthorization | null;
|
|
45
|
+
/** Defaults to the production processor. */
|
|
46
|
+
processorUrl?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Builds a `(Request) => Promise<Response>` you can mount in any fetch-based
|
|
50
|
+
* runtime (Next.js route handlers, Hono, Bun.serve, Cloudflare Workers).
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* // app/api/refund/route.ts
|
|
54
|
+
* export const POST = createRefundHandler({
|
|
55
|
+
* apiKey: process.env.RHINESTONE_API_KEY!,
|
|
56
|
+
* async authorize(request) {
|
|
57
|
+
* const session = await getSession(request);
|
|
58
|
+
* return session ? { depositRecipient: session.depositRecipient } : null;
|
|
59
|
+
* },
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function createRefundHandler(options: CreateRefundHandlerOptions): (request: Request) => Promise<Response>;
|
|
64
|
+
|
|
65
|
+
export { type CreateRefundHandlerOptions, type RefundAuthorization, createRefundHandler };
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
var DEFAULT_PROCESSOR_URL = "https://v1.orchestrator.rhinestone.dev/deposit-processor";
|
|
3
|
+
function jsonError(message, status) {
|
|
4
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
5
|
+
status,
|
|
6
|
+
headers: { "Content-Type": "application/json" }
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
function parseBody(value) {
|
|
10
|
+
if (typeof value !== "object" || value === null) return null;
|
|
11
|
+
const record = value;
|
|
12
|
+
const fields = ["chain", "txHash", "account", "token", "destination"];
|
|
13
|
+
for (const field of fields) {
|
|
14
|
+
if (typeof record[field] !== "string" || record[field] === "") return null;
|
|
15
|
+
}
|
|
16
|
+
return Object.fromEntries(
|
|
17
|
+
fields.map((field) => [field, record[field]])
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
function sameIdentifier(a, b) {
|
|
21
|
+
if (a === b) return true;
|
|
22
|
+
const isHex = (value) => /^0x[0-9a-fA-F]+$/.test(value);
|
|
23
|
+
return isHex(a) && isHex(b) && a.toLowerCase() === b.toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
function createRefundHandler(options) {
|
|
26
|
+
const processorUrl = (options.processorUrl ?? DEFAULT_PROCESSOR_URL).replace(
|
|
27
|
+
/\/$/,
|
|
28
|
+
""
|
|
29
|
+
);
|
|
30
|
+
const headers = {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
"x-api-key": options.apiKey
|
|
33
|
+
};
|
|
34
|
+
return async function handleRefund(request) {
|
|
35
|
+
const body = parseBody(await request.json().catch(() => null));
|
|
36
|
+
if (!body) {
|
|
37
|
+
return jsonError("Malformed refund request", 400);
|
|
38
|
+
}
|
|
39
|
+
const authorization = await options.authorize(request, body);
|
|
40
|
+
if (!authorization) {
|
|
41
|
+
return jsonError("Unauthorized", 401);
|
|
42
|
+
}
|
|
43
|
+
const query = new URLSearchParams({
|
|
44
|
+
recipient: authorization.depositRecipient,
|
|
45
|
+
chain: body.chain,
|
|
46
|
+
txHash: body.txHash,
|
|
47
|
+
includeSpam: "true"
|
|
48
|
+
});
|
|
49
|
+
const lookup = await fetch(`${processorUrl}/deposits?${query}`, { headers });
|
|
50
|
+
if (!lookup.ok) {
|
|
51
|
+
return jsonError("Could not verify the deposit", 502);
|
|
52
|
+
}
|
|
53
|
+
const { deposits = [] } = await lookup.json();
|
|
54
|
+
const owned = deposits.some(
|
|
55
|
+
(deposit) => deposit.depositAddress !== void 0 && deposit.token !== void 0 && sameIdentifier(deposit.depositAddress, body.account) && sameIdentifier(deposit.token, body.token)
|
|
56
|
+
);
|
|
57
|
+
if (!owned) {
|
|
58
|
+
return jsonError("Forbidden", 403);
|
|
59
|
+
}
|
|
60
|
+
const destination = authorization.refundDestination ?? body.destination;
|
|
61
|
+
const upstream = await fetch(`${processorUrl}/deposits/refund`, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers,
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
chain: body.chain,
|
|
66
|
+
txHash: body.txHash,
|
|
67
|
+
account: body.account,
|
|
68
|
+
token: body.token,
|
|
69
|
+
recipient: destination
|
|
70
|
+
})
|
|
71
|
+
});
|
|
72
|
+
const text = await upstream.text();
|
|
73
|
+
if (!upstream.ok) {
|
|
74
|
+
return new Response(text, {
|
|
75
|
+
status: upstream.status,
|
|
76
|
+
headers: { "Content-Type": "application/json" }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
let body_;
|
|
80
|
+
try {
|
|
81
|
+
body_ = JSON.parse(text);
|
|
82
|
+
} catch {
|
|
83
|
+
return new Response(text, {
|
|
84
|
+
status: upstream.status,
|
|
85
|
+
headers: { "Content-Type": "application/json" }
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return new Response(
|
|
89
|
+
JSON.stringify({ ...body_, destination }),
|
|
90
|
+
{ status: upstream.status, headers: { "Content-Type": "application/json" } }
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
createRefundHandler
|
|
96
|
+
};
|
package/dist/styles.css
CHANGED
|
@@ -327,7 +327,12 @@
|
|
|
327
327
|
the previous step's height instead of jumping. Min-height is on
|
|
328
328
|
modal-content rather than modal-body so the transitioned property
|
|
329
329
|
lives on an element that survives step swaps. */
|
|
330
|
-
min-height 280ms cubic-bezier(0.22, 1, 0.36, 1)
|
|
330
|
+
min-height 280ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
331
|
+
/* The Swapped iframe phase raises the shell corner to the widget's 28px
|
|
332
|
+
(see the sizing rules at the bottom of this file); animate it on the same
|
|
333
|
+
curve as max-width/min-height so the corner doesn't pop while the modal
|
|
334
|
+
is still resizing into the iframe. */
|
|
335
|
+
border-radius 240ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
331
336
|
}
|
|
332
337
|
|
|
333
338
|
.rs-modal-content[data-theme="dark"] {
|
|
@@ -557,6 +562,19 @@
|
|
|
557
562
|
gap: 6px;
|
|
558
563
|
}
|
|
559
564
|
|
|
565
|
+
/* Chrome/Safari paint their own autofill background with !important UA
|
|
566
|
+
styles. Clip that paint to the glyphs so every themed input keeps its real
|
|
567
|
+
surface — including the transparent withdrawal inputs — without replacing
|
|
568
|
+
the focus/error box-shadows. The standard pseudo-class and WebKit alias are
|
|
569
|
+
both kept for current and older browser engines. */
|
|
570
|
+
.rs-modal
|
|
571
|
+
:is(.rs-input, .rs-withdraw-input):is(:autofill, :-webkit-autofill) {
|
|
572
|
+
-webkit-background-clip: text;
|
|
573
|
+
background-clip: text;
|
|
574
|
+
-webkit-text-fill-color: var(--rs-foreground);
|
|
575
|
+
caret-color: var(--rs-foreground);
|
|
576
|
+
}
|
|
577
|
+
|
|
560
578
|
.rs-input-label {
|
|
561
579
|
font-size: 13px;
|
|
562
580
|
color: var(--rs-muted);
|
|
@@ -2555,7 +2573,8 @@
|
|
|
2555
2573
|
the image is loading (`data-loading`), show a muted pulsing placeholder so the
|
|
2556
2574
|
first paint isn't a blank/broken-image flash; it inherits the caller's size
|
|
2557
2575
|
and border-radius, so there's no layout shift when the icon paints in. */
|
|
2558
|
-
.rs-token-icon[data-loading]
|
|
2576
|
+
.rs-token-icon[data-loading],
|
|
2577
|
+
.rs-cdn-icon[data-loading] {
|
|
2559
2578
|
background-color: var(--rs-muted);
|
|
2560
2579
|
animation: rs-token-icon-pulse 1.2s ease-in-out infinite;
|
|
2561
2580
|
}
|
|
@@ -2571,7 +2590,8 @@
|
|
|
2571
2590
|
}
|
|
2572
2591
|
|
|
2573
2592
|
@media (prefers-reduced-motion: reduce) {
|
|
2574
|
-
.rs-token-icon[data-loading]
|
|
2593
|
+
.rs-token-icon[data-loading],
|
|
2594
|
+
.rs-cdn-icon[data-loading] {
|
|
2575
2595
|
animation: none;
|
|
2576
2596
|
}
|
|
2577
2597
|
}
|
|
@@ -4867,6 +4887,53 @@
|
|
|
4867
4887
|
object-fit: cover;
|
|
4868
4888
|
}
|
|
4869
4889
|
|
|
4890
|
+
/* Provider-hosted fiat method icons (regional methods: GCash, Maya, PIX, …).
|
|
4891
|
+
When a dark-background variant is supplied, the theme toggles which img
|
|
4892
|
+
shows; otherwise the single (light) icon shows in both themes. */
|
|
4893
|
+
.rs-fiat-remote-icon {
|
|
4894
|
+
display: inline-flex;
|
|
4895
|
+
align-items: center;
|
|
4896
|
+
justify-content: center;
|
|
4897
|
+
width: 24px;
|
|
4898
|
+
height: 24px;
|
|
4899
|
+
}
|
|
4900
|
+
|
|
4901
|
+
.rs-fiat-remote-icon img {
|
|
4902
|
+
width: 24px;
|
|
4903
|
+
height: 24px;
|
|
4904
|
+
object-fit: contain;
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4907
|
+
.rs-fiat-remote-icon-dark {
|
|
4908
|
+
display: none;
|
|
4909
|
+
}
|
|
4910
|
+
|
|
4911
|
+
.rs-modal[data-theme="dark"] .rs-fiat-remote-icon--has-dark .rs-fiat-remote-icon-light {
|
|
4912
|
+
display: none;
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4915
|
+
.rs-modal[data-theme="dark"] .rs-fiat-remote-icon--has-dark .rs-fiat-remote-icon-dark {
|
|
4916
|
+
display: inline-block;
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
/* Sits in the row's meta slot, so no inline-with-the-label wrapper is needed —
|
|
4920
|
+
`.rs-list-row-title` already handles its own truncation. */
|
|
4921
|
+
.rs-fiat-method-badge {
|
|
4922
|
+
flex-shrink: 0;
|
|
4923
|
+
padding: 2px 6px;
|
|
4924
|
+
border-radius: 999px;
|
|
4925
|
+
background: #a4f4cf;
|
|
4926
|
+
color: #004f3b;
|
|
4927
|
+
font-size: 11px;
|
|
4928
|
+
font-weight: 600;
|
|
4929
|
+
line-height: 1.2;
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4932
|
+
.rs-modal[data-theme="dark"] .rs-fiat-method-badge {
|
|
4933
|
+
background: #006045;
|
|
4934
|
+
color: #d0fae5;
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4870
4937
|
.rs-list-row-body {
|
|
4871
4938
|
flex: 1;
|
|
4872
4939
|
min-width: 0;
|
|
@@ -4949,6 +5016,18 @@
|
|
|
4949
5016
|
border-radius: 4px;
|
|
4950
5017
|
}
|
|
4951
5018
|
|
|
5019
|
+
/* Chain with no bundled icon: short-label chip inside the badge circle. */
|
|
5020
|
+
.rs-chain-badge-fallback {
|
|
5021
|
+
display: inline-flex;
|
|
5022
|
+
width: 100%;
|
|
5023
|
+
height: 100%;
|
|
5024
|
+
align-items: center;
|
|
5025
|
+
justify-content: center;
|
|
5026
|
+
font-size: 9px;
|
|
5027
|
+
font-weight: 600;
|
|
5028
|
+
line-height: 1;
|
|
5029
|
+
}
|
|
5030
|
+
|
|
4952
5031
|
/* Card-network chips: exported Figma tiles are all 38×24. */
|
|
4953
5032
|
.rs-list-row-meta-icons--cards > .rs-card-network {
|
|
4954
5033
|
position: relative;
|
|
@@ -5068,8 +5147,8 @@
|
|
|
5068
5147
|
}
|
|
5069
5148
|
|
|
5070
5149
|
/* The iframe phase owns the whole modal width. Normal screens keep the
|
|
5071
|
-
16px body inset, but
|
|
5072
|
-
|
|
5150
|
+
16px body inset, but the embedded sheet should not look like a padded card
|
|
5151
|
+
inside our modal. */
|
|
5073
5152
|
.rs-modal-body[data-flow-mode="fiat-onramp"]:has(.rs-fiat-onramp),
|
|
5074
5153
|
.rs-modal-body[data-flow-mode="exchange-connect"]:has(.rs-fiat-onramp) {
|
|
5075
5154
|
padding-inline: 0;
|
|
@@ -5091,7 +5170,9 @@
|
|
|
5091
5170
|
width: 100%;
|
|
5092
5171
|
min-height: 586px;
|
|
5093
5172
|
margin: 0;
|
|
5094
|
-
|
|
5173
|
+
/* Matches the iframe's 28px below so the loading / error overlays that sit
|
|
5174
|
+
on top of it share the same corner arc. */
|
|
5175
|
+
border-radius: 28px;
|
|
5095
5176
|
overflow: hidden;
|
|
5096
5177
|
}
|
|
5097
5178
|
|
|
@@ -5099,13 +5180,29 @@
|
|
|
5099
5180
|
min-height: 585px;
|
|
5100
5181
|
}
|
|
5101
5182
|
|
|
5183
|
+
/* Swapped paints its widget as a card with a 28px corner radius and its drop
|
|
5184
|
+
shadow sits just outside that arc, so the embedding iframe has to clip at the
|
|
5185
|
+
same 28px — otherwise the shadow's corners bleed out past the card and read as
|
|
5186
|
+
grey smudges in our modal corners. Confirmed with Swapped: their iframe-
|
|
5187
|
+
initialization docs still show a stale `border-radius: 0.75rem`, 28px is the
|
|
5188
|
+
value that matches the current widget. */
|
|
5102
5189
|
.rs-fiat-onramp-iframe {
|
|
5103
5190
|
display: block;
|
|
5104
5191
|
width: 100%;
|
|
5105
5192
|
height: 586px;
|
|
5106
5193
|
min-height: 586px;
|
|
5107
5194
|
border: 0;
|
|
5195
|
+
border-radius: 28px;
|
|
5108
5196
|
background: var(--rs-background);
|
|
5197
|
+
/* Swapped's widget paints its own page background behind a rounded card, and
|
|
5198
|
+
that card is rounder than our modal. Where their radius exceeds ours the
|
|
5199
|
+
modal's corner clip doesn't reach their page background, so a grey wedge
|
|
5200
|
+
shows in each corner (visible on their payment-method screen). Clipping the
|
|
5201
|
+
iframe at a radius at least as round as their card puts the modal's own
|
|
5202
|
+
background there instead.
|
|
5203
|
+
ponytail: one blunt radius rather than tracking Swapped's per-screen shape
|
|
5204
|
+
— if they round their card past this, raise it. */
|
|
5205
|
+
border-radius: 28px;
|
|
5109
5206
|
/* Swapped's embedded widget has no theme URL param and follows
|
|
5110
5207
|
`prefers-color-scheme`. Setting `color-scheme` on the iframe element
|
|
5111
5208
|
propagates into its (cross-origin) document's `prefers-color-scheme`, so
|
|
@@ -5129,6 +5226,15 @@
|
|
|
5129
5226
|
min-height: 585px;
|
|
5130
5227
|
}
|
|
5131
5228
|
|
|
5229
|
+
/* Connect sits below our header (only fiat is immersive), so only its bottom
|
|
5230
|
+
corners meet the modal's rounded edge. Rounding the top too would leave the
|
|
5231
|
+
shell background showing through as light notches under the header. Applies to
|
|
5232
|
+
the wrap as well so the loading / error overlays share the same arc. */
|
|
5233
|
+
.rs-fiat-onramp[data-variant="connect"] .rs-fiat-onramp-iframe-wrap,
|
|
5234
|
+
.rs-fiat-onramp[data-variant="connect"] .rs-fiat-onramp-iframe {
|
|
5235
|
+
border-radius: 0 0 28px 28px;
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5132
5238
|
/* Finalising deposit — compact centered spinner screen (Swapped Connect). */
|
|
5133
5239
|
.rs-finalising-body {
|
|
5134
5240
|
display: flex;
|
|
@@ -5325,6 +5431,16 @@
|
|
|
5325
5431
|
line-height: 1.35;
|
|
5326
5432
|
}
|
|
5327
5433
|
|
|
5434
|
+
/* Pre-deposit "Exchange Fee" disclosure shown below the picker footer. */
|
|
5435
|
+
.rs-exchange-select-fee-note {
|
|
5436
|
+
font-size: 12px;
|
|
5437
|
+
font-weight: 500;
|
|
5438
|
+
line-height: 1.35;
|
|
5439
|
+
color: var(--rs-muted);
|
|
5440
|
+
text-align: center;
|
|
5441
|
+
margin: 0;
|
|
5442
|
+
}
|
|
5443
|
+
|
|
5328
5444
|
/* =============================================================================
|
|
5329
5445
|
Modal sizing for the Swapped iframe step
|
|
5330
5446
|
=============================================================================
|
|
@@ -5368,16 +5484,32 @@
|
|
|
5368
5484
|
min-height: 698px;
|
|
5369
5485
|
}
|
|
5370
5486
|
|
|
5371
|
-
/* Iframe phase:
|
|
5372
|
-
embedded iframe exactly
|
|
5373
|
-
|
|
5374
|
-
|
|
5487
|
+
/* Iframe phase: fiat is immersive (no header/footer chrome), so the modal is
|
|
5488
|
+
sized to the embedded iframe exactly — 586 — and fills top-to-bottom with no
|
|
5489
|
+
whitespace. The taller setup values above pre-reserve the space before
|
|
5490
|
+
SwappedIframeStep mounts. */
|
|
5375
5491
|
.rs-modal-content:has(.rs-modal-body[data-flow-mode="fiat-onramp"] .rs-fiat-onramp) {
|
|
5376
5492
|
min-height: 586px;
|
|
5377
5493
|
}
|
|
5378
5494
|
|
|
5495
|
+
/* Connect keeps our header, so its exact height is the sum of the chrome above
|
|
5496
|
+
the widget: 16px top inset + 24px header row + 16px gap + 585px iframe + 2px
|
|
5497
|
+
borders (border-box). Matching it here means the setup → iframe hand-off
|
|
5498
|
+
settles on one height instead of overshooting and snapping back. */
|
|
5379
5499
|
.rs-modal-content:has(.rs-modal-body[data-flow-mode="exchange-connect"] .rs-fiat-onramp) {
|
|
5380
|
-
min-height:
|
|
5500
|
+
min-height: 643px;
|
|
5501
|
+
}
|
|
5502
|
+
|
|
5503
|
+
/* In the iframe phase the Swapped widget owns the modal's bottom edge (fiat owns
|
|
5504
|
+
all four corners; Connect owns the bottom two, under our header), so the shell
|
|
5505
|
+
corner takes the widget's 28px instead of our default 16px. Left at 16px, our
|
|
5506
|
+
border would trace a tighter arc than the widget inside it and those corners
|
|
5507
|
+
would show a sliver of modal background between the two curves — plus the
|
|
5508
|
+
widget's own drop shadow. Kept uniform so the shell stays one shape, and
|
|
5509
|
+
scoped to this phase only — every other screen keeps 16px. */
|
|
5510
|
+
.rs-modal-content:has(.rs-modal-body[data-flow-mode="fiat-onramp"] .rs-fiat-onramp),
|
|
5511
|
+
.rs-modal-content:has(.rs-modal-body[data-flow-mode="exchange-connect"] .rs-fiat-onramp) {
|
|
5512
|
+
border-radius: 28px;
|
|
5381
5513
|
}
|
|
5382
5514
|
|
|
5383
5515
|
/* =============================================================================
|
|
@@ -5621,15 +5753,17 @@
|
|
|
5621
5753
|
}
|
|
5622
5754
|
|
|
5623
5755
|
/* Swapped/fiat/exchange flows pre-reserve a tall desktop iframe height
|
|
5624
|
-
(min-height 585-699px)
|
|
5625
|
-
the sheet taller than the screen and
|
|
5626
|
-
|
|
5627
|
-
|
|
5756
|
+
(min-height 585-699px), a 445px max-width, and a 28px shell radius. On a
|
|
5757
|
+
phone that would force the sheet taller than the screen and round its
|
|
5758
|
+
bottom corners away from the viewport edge, so restore the bottom-sheet
|
|
5759
|
+
dimensions and top-only radius. Specificity (0,4,0) matches the iframe
|
|
5760
|
+
phase :has() rules and, appended last, wins the tie. */
|
|
5628
5761
|
.rs-modal-overlay .rs-modal-content:has(.rs-modal-body[data-flow-mode="fiat-onramp"]),
|
|
5629
5762
|
.rs-modal-overlay .rs-modal-content:has(.rs-modal-body[data-flow-mode="exchange-connect"]) {
|
|
5630
5763
|
max-width: none;
|
|
5631
5764
|
min-height: 0;
|
|
5632
5765
|
max-height: min(92dvh, calc(100dvh - 24px));
|
|
5766
|
+
border-radius: 16px 16px 0 0;
|
|
5633
5767
|
}
|
|
5634
5768
|
|
|
5635
5769
|
/* The history panel is a long, scrollable list overlaid position:absolute, so
|