diff --git a/.env b/.env index dacbf5ca7c3..4dd4f90ced3 100644 --- a/.env +++ b/.env @@ -232,6 +232,7 @@ VITE_STARKNET_NODE_URL=https://rpc.starknet.lava.build VITE_STORY_NODE_URL=https://mainnet.storyrpc.io VITE_SUI_NODE_URL=https://fullnode.mainnet.sui.io:443 VITE_TON_NODE_URL=https://toncenter.com/api/v2/jsonRPC +VITE_TRON_GRID_API_KEY=17430894-392e-44e8-b015-4a9c4fe17546 VITE_TRON_NODE_URL=https://api.trongrid.io VITE_UNICHAIN_NODE_URL=https://mainnet.unichain.org VITE_WORLDCHAIN_NODE_URL=https://worldchain-mainnet.g.alchemy.com/public @@ -350,6 +351,7 @@ VITE_TENDERLY_PROJECT_SLUG=project VITE_SWAPS_SERVER_URL=https://api.swap-service.shapeshift.com VITE_USER_SERVER_URL=https://api.user-service.shapeshift.com VITE_NOTIFICATIONS_SERVER_URL=https://api.notifications-service.shapeshift.com +VITE_PUBLIC_API_URL=https://api.shapeshift.com # Yield.xyz VITE_YIELD_XYZ_API_KEY=06903960-e442-4870-81eb-03ff3ad4c035 diff --git a/.env.development b/.env.development index c6399c767d6..9f5bd818cf2 100644 --- a/.env.development +++ b/.env.development @@ -97,14 +97,10 @@ VITE_THORCHAIN_MIDGARD_URL=https://dev-api.thorchain.shapeshift.com/midgard/v2 VITE_MAYACHAIN_MIDGARD_URL=https://dev-api.mayachain.shapeshift.com/midgard/v2 # Webservices -# Remote dev servers (default - works for all developers) VITE_SWAPS_SERVER_URL=https://dev-api.swap-service.shapeshift.com VITE_USER_SERVER_URL=https://dev-api.user-service.shapeshift.com VITE_NOTIFICATIONS_SERVER_URL=https://dev-api.notifications-service.shapeshift.com -# Local development via Vite proxy (uncomment to use localhost backend) -# VITE_SWAPS_SERVER_URL=/swaps-api -# VITE_USER_SERVER_URL=/user-api -# VITE_NOTIFICATIONS_SERVER_URL=/notifications-api +VITE_PUBLIC_API_URL=https://dev-api.shapeshift.com # Proxy API VITE_PROXY_API_BASE_URL=https://dev-api.proxy.shapeshift.com diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000000..0ea7cbbcd7b --- /dev/null +++ b/TODO.md @@ -0,0 +1,527 @@ +# TODO + +Untracked working notes — pending work pulled from auto-memory plus the in-flight +refactor identified in this session. Not for commit. + +--- + +## Refactor: split `*TransactionMetadata` from `SwapperSpecificMetadata` + +**Origin**: 2026-05-08 review of `packages/swapper/src/types.ts` while threading +THORChain transaction data through the public-api. + +**Problem**: today's `*TransactionMetadata` structs (e.g. `RelayTransactionMetadata`, +`DebridgeTransactionMetadata`, `nearIntentsSpecific`) bag two unrelated concerns +into a single shape: + +1. **Build-time tx params** — `to`, `data`, `value`, `gasLimit`, `psbt`, `opReturnData` +2. **Post-submission tracking tokens** — `relayId`, `orderId`, `isSameChainSwap`, etc. + +The same struct lives on both `TradeQuoteStep` (where the build half is consumed) +and on `Swap.metadata` / `StoredQuote.metadata` via `SwapperSpecificMetadata` (where +only the tracking half is consumed). Each phase ignores the irrelevant half. + +**Cruft confirmed**: `acrossTransactionMetadata` is copied into +`SwapperSpecificMetadata` (`packages/public-api/src/routes/quote/getQuote.ts:202`) +but `AcrossSwapper.checkTradeStatus` (`endpoints.ts:71-140`) never reads it — it +polls by tx hash. Field is fully dead post-submission. + +**Plan**: + +1. Split each `*TransactionMetadata` into `*TxBuild` (step-only) and + `*Tracking` (swap-only). +2. Keep build halves on `TradeQuoteStep`. Keep tracking halves on + `SwapperSpecificMetadata`. +3. Update all `step.TransactionMetadata` consumers + (`packages/swapper/src/swappers/*/endpoints.ts`, + `packages/public-api/src/routes/quote/extractTransactionData.ts`) to read + from the build half. +4. Update all `swap.metadata.TransactionMetadata` consumers + (`RelaySwapper/endpoints.ts:189-223`, `DebridgeSwapper/endpoints.ts:140`, + `useSwapActionSubscriber.tsx:256`) to read from the tracking half. +5. Drop `acrossTransactionMetadata` from `SwapperSpecificMetadata` entirely + + stop copying it in `getQuote.ts`. + +**Why bother**: real, modest readability win — types tell the truth about +lifecycle. Step has only what's needed to build a tx; swap has only what's needed +to track one. `thorchainTransactionMetadata` (just added) is already on the clean +side of this and stays step-only. + +**Cost**: medium churn. Touches all `*Swapper` packages plus `public-api`. +Defer until the THORChain widget integration work has settled. + +--- + +## Quote expiry enforcement (widget + public-api contract) + +**Origin**: 2026-05-08 discussion of why the swap-widget doesn't call +`swapper.getUnsigned*Transaction` at sign time. Conclusion: trusting the +public-api response is correct (external consumers can't reach the swapper +directly), but staleness must be guarded by an enforced quote expiry. + +**Problem**: today's expiry data is plumbed but not load-bearing. + +- THORNode returns an `expiry` on `/quote/swap`; we capture it on + `thorchainTransactionMetadata.expiry` + (`packages/swapper/src/types.ts:427`, set in + `packages/swapper/src/thorchain-utils/getL1RateOrQuote.ts:301,348`). +- Public-api response carries an envelope-level `expiresAt` + (`packages/public-api/src/routes/quote/types.ts:152`), but it's the + **server cache TTL** (`QuoteStore.QUOTE_TTL_MS` = 15 min), not the + THORChain vault expiry. Server TTL can outlast the network's accept window. +- `QuoteStepSchema` exposes no expiry — external API consumers can't see + the binding deadline even if they want to honor it. +- Widget mirrors `expiresAt` in its types + (`packages/swap-widget/src/types/index.ts:222`) but never reads it: no + countdown, no pre-sign guard, no auto re-quote. + +**Risk**: signing against a rotated THORChain vault (funds lost/delayed) +or against stale fees. + +**Plan**: + +1. Surface per-step expiry on the public-api response. Either lift + `thorchainTransactionMetadata.expiry` onto the step, or compute envelope + `expiresAt = min(server TTL, all step expiries)` so the envelope is the + binding deadline. Latter keeps the contract surface small. +2. Widget: block sign when `Date.now() >= expiresAt`, auto-refetch quote, + show a countdown in the UI. +3. Document the contract for external API consumers in the public-api + README: "do not sign after `expiresAt`; re-fetch the quote." + +**Note**: the existing "getQuote: quote expiry mismatch" item in the +edge-cases list (response says 1 min, stored 15 min) is a related but +distinct bug — this section is the broader design fix that subsumes it. + +--- + +## THORChain widget integration — remaining gaps + +- **swap-widget execution hook**: `useSwapExecution.ts` throws "not yet + supported" for `utxo_psbt` and `cosmos` `transactionData.type` values. +- **THORChain native (RUNE/TCY/RUJI)** — uses `MsgDeposit` with no `to` + address. Current `CosmosTransactionData` type doesn't model this shape, so + cosmos execution can't dispatch THORChain native swaps. + +--- + +## swap-widget execution/approval refactor (deferred) + +`useSwapExecution.ts` and `useSwapApproval.ts` should move from `useEffect`-driven +("watch for `stateValue === 'executing'` and react") to imperative async functions +returned for direct `onClick` invocation. Local `isPending` replaces the +`executingRef`/`approvingRef` guards; try/catch at the call site replaces the +actor-send error dispatch. + +**Why**: execution is a user-initiated mutation. Modeling it as effect-driven +inverts the call flow, requires refs to guard against dep-array re-fires that +wouldn't exist outside an effect, and forces awkward eslint dep choices. +Discussed and parked 2026-05-07 after cleaning up the existing hook (THORChain +pass-through + dispatch on `transactionData.type` + `getErrorMessage` helper). + +**Before refactoring, verify**: + +1. Who currently sends `FETCH_QUOTE` / `APPROVE`? The machine auto-transitions + `quoting → executing` on `QUOTE_SUCCESS` and `approving → executing` on + `APPROVAL_SUCCESS` (`packages/swap-widget/src/machines/swapMachine.ts`). If + the UX is "quote success auto-fires execution" with no second confirm click, + the dispatcher of `QUOTE_SUCCESS` / `APPROVAL_SUCCESS` must also invoke the + new imperative `executeSwap()`, or the user gains a click they didn't have. +2. Is the `executing` machine state referenced for UI elsewhere (e.g. + "Submitting..." in `SwapWidget.tsx`)? If so, either keep tracking via local + `isPending` on the button or send an `EXECUTE_START` event into the machine + before awaiting. + +**Target shape**: + +```ts +export const useSwapExecution = () => { + const actorRef = SwapMachineCtx.useActorRef() + const { walletClient, walletAddress, bitcoin, solana } = useSwapWallet() + return useCallback(async () => { /* same body */ }, [actorRef, walletClient, walletAddress, bitcoin, solana]) +} +``` + +Caller: `const executeSwap = useSwapExecution()` → +`