@rhinestone/deposit-modal 0.18.0 → 0.20.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.
Files changed (83) hide show
  1. package/README.md +179 -48
  2. package/dist/BankOnrampStep-JG32OCM2.mjs +435 -0
  3. package/dist/BankOnrampStep-NPGXAA7U.cjs +435 -0
  4. package/dist/{DepositModalReown-VCY65DJX.mjs → DepositModalReown-3FGTPER6.mjs} +6 -5
  5. package/dist/{DepositModalReown-TVO3ZQQK.cjs → DepositModalReown-ALVWADG7.cjs} +21 -20
  6. package/dist/WithdrawModal-5YWFJSYY.cjs +12 -0
  7. package/dist/WithdrawModal-LL4Z5WZP.mjs +12 -0
  8. package/dist/{bearer-transport-CVHNBgoV.d.cts → bearer-transport-DZyeDG64.d.cts} +5 -1
  9. package/dist/{bearer-transport-CVHNBgoV.d.ts → bearer-transport-DZyeDG64.d.ts} +5 -1
  10. package/dist/{caip-BMi-9t09.d.cts → caip-nwFFL46o.d.cts} +2 -2
  11. package/dist/{caip-BMi-9t09.d.ts → caip-nwFFL46o.d.ts} +2 -2
  12. package/dist/{chunk-EKC2IRQG.mjs → chunk-247GJH57.mjs} +687 -299
  13. package/dist/{chunk-FTWKAJGN.mjs → chunk-2FOPO4N5.mjs} +1 -1
  14. package/dist/{chunk-AIZKGFVA.mjs → chunk-32ERBFMO.mjs} +1499 -978
  15. package/dist/chunk-5QUWEFD4.mjs +19 -0
  16. package/dist/chunk-6CTJFR3T.mjs +113 -0
  17. package/dist/{chunk-3TEVMP76.mjs → chunk-B2LZQIGB.mjs} +1 -1
  18. package/dist/{chunk-34JVJZEZ.cjs → chunk-BTEG2VNZ.cjs} +171 -130
  19. package/dist/{chunk-N3AAT6BS.cjs → chunk-CXRH5MKO.cjs} +1556 -1035
  20. package/dist/chunk-E7IMWVMN.mjs +346 -0
  21. package/dist/{chunk-N6JJG57R.cjs → chunk-EKFXUF5A.cjs} +1 -1
  22. package/dist/chunk-FKJ6EJHH.cjs +346 -0
  23. package/dist/chunk-FMUSY563.cjs +19 -0
  24. package/dist/{chunk-LVCD6RHF.cjs → chunk-FVJKUTHF.cjs} +2 -2
  25. package/dist/chunk-G264OM23.cjs +224 -0
  26. package/dist/{chunk-R2B6VD4E.mjs → chunk-HP7S7AD6.mjs} +73 -32
  27. package/dist/{chunk-O4JZKKCZ.cjs → chunk-OPT5EW6L.cjs} +10 -5
  28. package/dist/chunk-PYRXOP2Y.cjs +113 -0
  29. package/dist/{chunk-X4EUXBHJ.mjs → chunk-XBJCZYBX.mjs} +8 -3
  30. package/dist/chunk-YY4ZOAH5.mjs +224 -0
  31. package/dist/{chunk-JV5OGG53.cjs → chunk-Z4RBTNKX.cjs} +1202 -814
  32. package/dist/compliance.cjs +4 -7
  33. package/dist/compliance.d.cts +26 -3
  34. package/dist/compliance.d.ts +26 -3
  35. package/dist/compliance.mjs +6 -9
  36. package/dist/constants-BQDxVNgr.d.cts +29 -0
  37. package/dist/constants-DjGQW5N5.d.ts +29 -0
  38. package/dist/constants.cjs +3 -37
  39. package/dist/constants.d.cts +4 -20
  40. package/dist/constants.d.ts +4 -20
  41. package/dist/constants.mjs +4 -38
  42. package/dist/deposit.cjs +7 -6
  43. package/dist/deposit.d.cts +5 -3
  44. package/dist/deposit.d.ts +5 -3
  45. package/dist/deposit.mjs +6 -5
  46. package/dist/embed.cjs +21 -20
  47. package/dist/embed.d.cts +3 -2
  48. package/dist/embed.d.ts +3 -2
  49. package/dist/embed.mjs +7 -6
  50. package/dist/index.cjs +8 -35
  51. package/dist/index.d.cts +5 -3
  52. package/dist/index.d.ts +5 -3
  53. package/dist/index.mjs +9 -36
  54. package/dist/onramp.cjs +4 -20
  55. package/dist/onramp.d.cts +59 -23
  56. package/dist/onramp.d.ts +59 -23
  57. package/dist/onramp.mjs +5 -21
  58. package/dist/polymarket.cjs +6 -6
  59. package/dist/polymarket.mjs +2 -2
  60. package/dist/ramp-types-HYWd7CW0.d.cts +108 -0
  61. package/dist/ramp-types-HYWd7CW0.d.ts +108 -0
  62. package/dist/styles.css +280 -0
  63. package/dist/{types-CbUs4boj.d.cts → types-BxSrazbq.d.ts} +56 -3
  64. package/dist/{types-CZYypODQ.d.ts → types-CixUBhrM.d.cts} +56 -3
  65. package/dist/withdraw.cjs +6 -5
  66. package/dist/withdraw.d.cts +4 -3
  67. package/dist/withdraw.d.ts +4 -3
  68. package/dist/withdraw.mjs +5 -4
  69. package/package.json +9 -17
  70. package/dist/WithdrawModal-GR3HLYXU.cjs +0 -11
  71. package/dist/WithdrawModal-RMCYJJ4S.mjs +0 -11
  72. package/dist/chunk-ADEWTBSN.cjs +0 -19
  73. package/dist/chunk-EAGMY3QE.mjs +0 -962
  74. package/dist/chunk-OY5N3E6L.cjs +0 -962
  75. package/dist/chunk-PP4QE32S.mjs +0 -19
  76. package/dist/constants-BT4ctuHo.d.cts +0 -64
  77. package/dist/constants-D21ZdNOP.d.ts +0 -64
  78. package/dist/offramp.cjs +0 -25
  79. package/dist/offramp.d.cts +0 -28
  80. package/dist/offramp.d.ts +0 -28
  81. package/dist/offramp.mjs +0 -25
  82. package/dist/ramp-types-C6aUz4SZ.d.cts +0 -43
  83. package/dist/ramp-types-C6aUz4SZ.d.ts +0 -43
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # @rhinestone/deposit-modal
2
2
 
3
- React modal for cross-chain deposits and withdrawals via Rhinestone smart accounts.
3
+ React modal for Rhinestone Deposits: accept deposits from any chain, asset or payment method, and let users withdraw the same way.
4
4
 
5
- **[Documentation](https://docs.rhinestone.dev/deposits/widget/quickstart)** ·
6
- [Deposit modal](https://docs.rhinestone.dev/deposits/widget/deposit-modal) ·
7
- [Withdraw modal](https://docs.rhinestone.dev/deposits/widget/withdraw-modal) ·
8
- [Backend proxy](https://docs.rhinestone.dev/deposits/widget/backend) ·
9
- [Migration guide](https://docs.rhinestone.dev/deposits/widget/migration)
5
+ **[Documentation](https://docs.rhinestone.dev/deposits/overview/widget/quickstart)** ·
6
+ [Deposit modal](https://docs.rhinestone.dev/deposits/overview/widget/widget-configuration) ·
7
+ [Withdraw modal](https://docs.rhinestone.dev/deposits/crypto/withdraw-funds) ·
8
+ [Backend proxy](https://docs.rhinestone.dev/deposits/overview/widget/backend-setup) ·
9
+ [Migration guide](https://docs.rhinestone.dev/deposits/overview/widget/migration-guide)
10
10
 
11
11
  This README covers the common integrations. The full props reference lives in the
12
12
  docs.
@@ -43,6 +43,22 @@ console error pointing at the CSP. Hosts without an explicit `img-src` (or with
43
43
  > sufficient. They now come from the CDN, which drops ~20 kB (brotli) from the
44
44
  > bundle and gives every Rhinestone surface one source of truth.
45
45
 
46
+ ### Token metadata (`/assets`)
47
+
48
+ Token symbols, decimals, logos and USD prices come from the processor's asset
49
+ endpoint, looked up by exact chain and token address. **Your proxy must forward
50
+ both `GET /assets` and `QUERY /assets`** for new modal metadata and price
51
+ lookups. Explicit CORS `allowMethods` must include `GET` and `QUERY`. Small
52
+ batches use GET; larger ones use QUERY; a failed QUERY retries as GET sub-batches.
53
+ The browser modal does not use an API key.
54
+
55
+ Without `/assets`, prices are unavailable: request failures no longer fall
56
+ back to `GET /prices` or invent a price. Token logos render as text badges,
57
+ and symbols still come from the chain catalog and your QR token list. A
58
+ withdrawal of a token neither the catalog nor `/assets` knows is blocked
59
+ rather than signed with guessed decimals. Older modal releases may still use
60
+ the supported public `/prices` proxy route; do not remove it for those clients.
61
+
46
62
  All three modals also post session analytics to Rhinestone (see
47
63
  [Analytics](#analytics)), so a host with an explicit `connect-src` must allow the
48
64
  orchestrator alongside its own proxy:
@@ -114,6 +130,31 @@ time, it does not emit the `submitted` lifecycle event with the processor's
114
130
  internal tracking key. Its `complete` event reports the settled source and
115
131
  destination transaction hashes once they exist.
116
132
 
133
+ ## Checkout payments
134
+
135
+ Pass `payment` when the modal funds one merchant order:
136
+
137
+ ```tsx
138
+ <DepositModal
139
+ {...depositProps}
140
+ payment={{ salt: order.accountSalt, amount: order.amountBaseUnits }}
141
+ />
142
+ ```
143
+
144
+ Register the order's managed account on your server first, then pass its 32-byte
145
+ salt to the modal. The same salt must resolve to the account address stored with
146
+ the order. `amount` is the required **destination token amount in base units**,
147
+ not a USD amount. The modal asks `/quotes/preview` for an `exactOut` quote and
148
+ shows the source amount the payer should send; the backend proxy must forward
149
+ that route to a processor with `exactOut` support.
150
+
151
+ Reconcile using the account on processor webhooks or `GET /deposits?account=`.
152
+ `destinationAmount` on the `complete` event is the amount actually delivered in
153
+ destination token base units. Quotes are estimates, and a payer using a QR,
154
+ exchange, or card can send a different amount, so compare the delivered amount
155
+ with the order amount before marking it paid. Checkout mode withholds the
156
+ recipient's deposit history from the payer.
157
+
117
158
  ## Withdraw
118
159
 
119
160
  `WithdrawModal` lives at `@rhinestone/deposit-modal/withdraw` and reports through the same `onLifecycle` + `onError` callbacks. It needs no wallet: you give it the account holding the funds and a function that performs one transfer.
@@ -191,7 +232,7 @@ signature, so there is no privilege for a page to borrow.
191
232
 
192
233
  **Your proxy must forward `POST /deposits/recover`**, alongside the `GET /deposits`
193
234
  used for the lookup. Both are in the
194
- [reference proxy](https://docs.rhinestone.dev/deposits/widget/backend); a proxy
235
+ [reference proxy](https://docs.rhinestone.dev/deposits/overview/widget/backend-setup); a proxy
195
236
  missing the recover route 404s the recovery rather than degrading.
196
237
 
197
238
  Two properties fall out of signing that are worth knowing:
@@ -269,6 +310,14 @@ The flow: user picks a fiat option → modal registers a Rhinestone smart accoun
269
310
  - **Sponsored** (client has a funded sponsor pool for the source chain): user receives 1:1 — the on-ramped amount lands on the target chain exactly.
270
311
  - **Unsponsored**: the bridging cost is deducted from the on-ramped amount and shown as a "Bridging cost" line on the success screen.
271
312
 
313
+ > **Proxy routes.** Card and exchange checkouts use the unified ramp routes with
314
+ > your API key attached: `POST /onramp/orders`,
315
+ > `GET /onramp/options?provider=swapped` and `GET /onramp/orders/:id`. The last
316
+ > two are also bank customer routes: forward them with the key only when the
317
+ > request carries no `Authorization` header, and never key-forward the list
318
+ > `GET /onramp/orders`. [`deposit-widget-proxy`](https://github.com/rhinestonewtf/deposit-widget-proxy)
319
+ > does this from #38. Keep the `/onramp/swapped/*` routes for older modal versions.
320
+
272
321
  Host CSP must allow Swapped's domains:
273
322
 
274
323
  ```
@@ -283,9 +332,92 @@ frame-src https://widget.swapped.com https://sandbox.swapped.com https://connect
283
332
  | `enableQrTransfer` | `boolean` | `true` | Show the Transfer Crypto / QR row. Set false for fiat-only. |
284
333
  | `fiatMethods` | `FiatMethodsConfig` | regional | Optional exact subset of standard `payment_group`s. Providing it opts out of regional personalization; empty means none. |
285
334
 
335
+ ### Bank transfer accounts (Noah)
336
+
337
+ > **Preview.** Your proxy must forward these on the customer bearer before you ship
338
+ > a modal with `bankOnramp`: `GET /compliance/status`, `POST /compliance/verification`,
339
+ > `GET /onramp/options`, `POST`/`GET /onramp/accounts`,
340
+ > `GET /onramp/accounts/:id[/details]` and `GET /onramp/orders[/:id]`. A missing one
341
+ > 404s at the proxy. [`deposit-widget-proxy`](https://github.com/rhinestonewtf/deposit-widget-proxy)
342
+ > forwards all of them.
343
+ >
344
+ > The prop is opt-in and inert unless you pass it. Treat it as unstable until
345
+ > this note is removed.
346
+
347
+ Pass `bankOnramp` to use reusable USD/EUR account details when Noah is provisioned
348
+ for your organization. Integrator KYB happens directly with Noah; Rhinestone
349
+ enables provisioning after Noah confirms it. Card and Apple Pay continue through Swapped. A customer who has
350
+ not been verified is taken through hosted verification first; an existing active
351
+ account is resumed; otherwise the account is configured once. There is no account
352
+ selection: the customer's one account opens directly.
353
+
354
+ Rhinestone chooses the currency from the residence the customer declares during
355
+ verification: EUR inside SEPA, USD elsewhere. It is not configurable, and it does
356
+ not follow the customer's location. The options are read again once the customer
357
+ is approved, because only then is the residence known.
358
+
359
+ The modal checks `GET /onramp/options?provider=noah` before customer verification. A 404
360
+ (provisioning disabled, a declared residence Noah doesn't serve, or an older proxy)
361
+ routes bank transfer through Swapped. When Swapped's regional list has no bank transfer
362
+ either, the customer returns to the Cash methods with a notice and the row hidden.
363
+ A successful response keeps Noah exclusive, even when no currencies are available.
364
+ Authentication and service errors show a retry instead of switching providers.
365
+
366
+ Bank transfers appear in History, including transfers that never become chain
367
+ deposits. The History icon stays available during Noah verification and bank details;
368
+ new activity adds a dot without navigating away.
369
+ Opening History acknowledges that activity and clears the bank notification;
370
+ only a subsequent new transfer or stage change adds it again.
371
+ After entering the bank journey, History refreshes `GET /onramp/orders?provider=noah` every 30 seconds while the modal
372
+ is open and visible, and on focus or opening History. Discovery continues after a
373
+ transfer finishes so the next one is detected. A linked deposit shares the entry
374
+ and shows final settlement separately from the provider's delivery.
375
+ Each bank-transfer entry shows its amount, date and latest status rather than
376
+ individual provider stages. "Settled" means crypto reached the deposit account;
377
+ "Completed" requires the linked deposit's final settlement to complete.
378
+ Expanded fiat entries use the same explorer links as crypto History: the
379
+ incoming crypto deposit and, when available, final settlement on its destination
380
+ chain. No on-chain link is shown for a fiat-only or sub-fee transfer.
381
+
382
+ ```tsx
383
+ <DepositModal
384
+ bankOnramp={{
385
+ supportUrl: "https://your-app.example/support", // Optional bank-transfer help.
386
+ getSession: (smartAccount) =>
387
+ authenticatedSessionClient.getCustomerSession({ smartAccount }),
388
+ }}
389
+ // ...
390
+ />
391
+ ```
392
+
393
+ `getSession` must call your authenticated server endpoint, which mints a
394
+ short-lived customer session with `createCustomerSessionHandler`. It is called
395
+ for every request; do not put the Rhinestone API key in the browser.
396
+
397
+ Bank instructions render inside the modal from `GET /onramp/accounts/:id/details`
398
+ on the customer bearer: separate transfer rails (SWIFT, ACH, Wire, SEPA), copy
399
+ controls, an optional payment reference, and the last-quoted fee with its
400
+ capture date. The modal holds coordinates only in component state and clears
401
+ them on exit. Do not send them to analytics or persist them in the host
402
+ application. A `409` means the account predates snapshot capture and the
403
+ processor backfill has not run; the customer is told to check back. Only identity
404
+ verification uses Noah's hosted page.
405
+
406
+ `bankOnramp` enables Cash when `fiatMethods` is omitted. When an exact
407
+ `fiatMethods` subset is provided, it must include
408
+ `{ "bank-transfer": true }`. The proxy must forward the `/onramp/options`,
409
+ `/onramp/accounts` and `/onramp/orders` customer routes with the bearer token
410
+ and without attaching its API key.
411
+
412
+ Hosts with a strict CSP must allow Noah Checkout:
413
+
414
+ ```
415
+ frame-src https://checkout.noah.com https://checkout.sandbox.noah.com;
416
+ ```
417
+
286
418
  ## Fund from Exchange (Swapped Connect)
287
419
 
288
- Pass `enableExchangeConnect` to expose a "Fund from Exchange" row alongside the existing options. Clicking it opens an exchange picker backed by `/onramp/swapped/connect-exchanges`; after the user picks an exchange, the modal embeds Swapped's Connect iframe for that selected `connection`, signs in, and Swapped pulls the crypto on-chain to the smart account.
420
+ Pass `enableExchangeConnect` to expose a "Fund from Exchange" row alongside the existing options. Clicking it opens an exchange picker backed by `GET /onramp/options?provider=swapped&kind=exchange` (see the proxy routes under [Fiat on-ramp](#fiat-on-ramp-swapped)); after the user picks an exchange, the modal embeds Swapped's Connect iframe for that selected `connection`, signs in, and Swapped pulls the crypto on-chain to the smart account.
289
421
 
290
422
  ```tsx
291
423
  <DepositModal
@@ -337,7 +469,7 @@ integration now posts analytics too, so it needs the `connect-src` above.
337
469
  Upgrading from 0.8.x is a breaking change on both modals.
338
470
 
339
471
  See [`MIGRATION.md`](./MIGRATION.md), or the same guide
340
- [in the docs](https://docs.rhinestone.dev/deposits/widget/migration).
472
+ [in the docs](https://docs.rhinestone.dev/deposits/overview/widget/migration-guide).
341
473
 
342
474
  ## Contributing
343
475
 
@@ -348,9 +480,9 @@ Local setup, the release process and where the docs live are in
348
480
 
349
481
  MIT
350
482
 
351
- ## Onramp and offramp services
483
+ ## Onramp service
352
484
 
353
- Deploy an updated `deposit-widget-proxy` before enabling these opt-in clients.
485
+ Deploy an updated `deposit-widget-proxy` before enabling this opt-in client.
354
486
  Custom proxies must allow Authorization in CORS and forward customer routes
355
487
  without injecting `x-api-key`. Never proxy `POST /compliance/sessions` publicly.
356
488
 
@@ -361,12 +493,11 @@ and returns `{ externalCustomerId, smartAccount? }` from trusted server state.
361
493
  Mount its returned handler on your authenticated POST endpoint. It returns only
362
494
  the 15-minute `token` and `expiresAt`, with no-store response headers.
363
495
 
364
- Use separate scoped clients for onramp and offramp. The transport is internal;
365
- the package does not expose a generic customer service or arbitrary request method.
496
+ The client is scoped to the customer's bearer. The transport is internal; the
497
+ package does not expose a generic customer service or arbitrary request method.
366
498
 
367
499
  ```ts
368
500
  import { createOnrampService } from '@rhinestone/deposit-modal/onramp';
369
- import { createOfframpService } from '@rhinestone/deposit-modal/offramp';
370
501
  import { getComplianceStatus } from '@rhinestone/deposit-modal/compliance';
371
502
 
372
503
  const options = {
@@ -374,44 +505,44 @@ const options = {
374
505
  getSession: () => authenticatedSessionClient.getCustomerSession(),
375
506
  };
376
507
  const onramp = createOnrampService(options);
377
- const offramp = createOfframpService(options);
378
- const interaction = await onramp.createSession({
379
- purpose: 'account_setup',
380
- country: 'DE',
381
- currency: 'EUR',
382
- paymentMethod: 'bank_transfer',
383
- });
384
- // The hosted journey handles verification through interaction.nextAction.
508
+ // Exactly one option, by region: EUR inside SEPA, USD elsewhere.
509
+ const { options: available } = await onramp.getOptions();
510
+ const { account } = await onramp.createAccount({ currency: available[0].currency });
511
+ // Setup completes in this call and returns the account it configured.
512
+ // Show the customer their bank details through a separate, repeatable call:
513
+ const { bankDetails } = await onramp.getAccountDetails(account.id);
514
+ // Each transfer into the account becomes an order, tracked stage by stage:
515
+ const { items: orders } = await onramp.listOrders({ limit: 10 });
385
516
  const status = await getComplianceStatus(options);
386
517
  ```
387
518
 
388
- The onramp client has `createSession`, `getSession`, `getOptions`, `listAccounts`,
389
- `getAccount`, `listPayments` and `getPayment`. The offramp client has the same
390
- session/options/payment methods, without accounts. `getComplianceStatus` retains
391
- the existing `/compliance/status` read separately; it is not a preliminary step.
392
- Shared types include `RampSession`, `RampOption` and `RampCurrency`.
393
- `CustomerSession` remains the browser bearer/expiry pair. Catch `ScopedRequestError`
394
- and inspect its `status` for HTTP failures; response bodies are not included.
519
+ The client has `getOptions`, `createAccount`, `listAccounts`, `getAccount`,
520
+ `getAccountDetails` and `listOrders`, on the unified
521
+ `/onramp/options`, `/onramp/accounts` and `/onramp/orders` routes.
522
+ `getComplianceStatus` retains the existing `/compliance/status` read separately;
523
+ it is not a preliminary step. Shared types include `OnrampOrder` (with its
524
+ `ReceiptStage`, `ConversionStage`, `DeliveryStage` and `ReturnStage`),
525
+ `OnrampAccount`, `RampOption` and `RampCurrency`. `CustomerSession` remains the
526
+ browser bearer/expiry pair. Catch `ScopedRequestError` and inspect its `status`
527
+ for HTTP failures; response bodies are not included.
395
528
 
396
- The three lightweight subpaths replace the unpublished `/customer` entry point.
397
- They do not import React, wallet connectors or the server-only token helper.
529
+ The lightweight subpaths do not import React, wallet connectors or the
530
+ server-only token helper.
398
531
 
399
532
  `getSession` is called per request. Keep tokens in memory, scoped to the logged-in
400
- user, and refresh them through your authenticated server. The clients reject
401
- expired sessions and never fall back to an application key. They make no calls
533
+ user, and refresh them through your authenticated server. The client rejects
534
+ expired sessions and never falls back to an application key. It makes no calls
402
535
  until explicitly invoked, so existing modals and Swapped flows remain unchanged.
403
- There is no preliminary capability request. Support/recovery methods belong to
404
- the future provider lifecycle implementation and are not exposed here.
405
-
406
- The initial contracts support EUR/USD bank transfers only. Onramp hosted setup
407
- uses `purpose: 'account_setup'` without an amount; the actual transfer determines
408
- fiat sent. Offramp hosted beneficiary collection uses
409
- `purpose: 'beneficiary_collection'`, separately from quoting and funding.
410
- Offramp options use `amount` as decimal fiat major units the recipient receives.
411
- No crypto-input quoting is exposed. Accounts return safe references/config/status,
412
- not bank instructions. Hosted interaction completion never means payment
413
- settlement; funding, payout and late returns have independent status fields.
414
-
415
- The provider adapter and journey execution are not connected by this release.
416
- Expect 503 until those implementations are deployed. Render hosted URLs only
417
- after the journey's iframe presentation has been verified; do not log/store URLs.
536
+
537
+ **An account setup is not a session.** It completes in the call that creates it
538
+ and returns `{ account }` — no status to poll, no expiry, no `nextAction`. Bank
539
+ details are read separately through `getAccountDetails`, because a customer
540
+ reads them before every transfer while setup happens once; that call returns
541
+ the stored instructions (`OnrampBankDetails`) and never calls the provider.
542
+
543
+ **An order is complete only once its crypto landed.** `stages.delivery` is
544
+ `submitted` when the provider reports the transfer and `completed`, with a
545
+ `depositId`, only once chain ingestion saw it. Show "delivered" for nothing
546
+ weaker. The initial contracts support EUR/USD bank transfers only; setup takes
547
+ a currency and no amount, because the actual transfer determines fiat sent. Do
548
+ not log or store hosted URLs.