@ninsei-labs/sdk 0.39.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/CHANGELOG.md +866 -0
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/engine/atomic/dleq2.js +330 -0
- package/engine/atomic/halfenc.js +205 -0
- package/engine/atomic/halves.js +119 -0
- package/engine/atomic/order-client.js +57 -0
- package/engine/atomic/order.js +132 -0
- package/engine/atomic/orderContext.js +70 -0
- package/engine/atomic/orderHalfCommit.js +29 -0
- package/engine/core/chainSource.js +306 -0
- package/engine/core/clock.js +40 -0
- package/engine/core/config.js +885 -0
- package/engine/core/finality.js +93 -0
- package/engine/core/format.js +168 -0
- package/engine/core/preSignGuards.js +495 -0
- package/engine/core/rfqSource.js +193 -0
- package/engine/core/sellWindow.js +43 -0
- package/engine/core/store.js +100 -0
- package/engine/core/swap.js +1153 -0
- package/engine/evm/amounts.js +131 -0
- package/engine/evm/auth.js +156 -0
- package/engine/evm/claimGas.js +87 -0
- package/engine/evm/depositor.js +127 -0
- package/engine/evm/dex.js +836 -0
- package/engine/evm/escrow.js +208 -0
- package/engine/evm/factory.js +149 -0
- package/engine/evm/fees.js +90 -0
- package/engine/evm/funding.js +309 -0
- package/engine/evm/gasReserve.js +88 -0
- package/engine/evm/index.js +123 -0
- package/engine/evm/injected.js +175 -0
- package/engine/evm/permit.js +125 -0
- package/engine/evm/prices.js +179 -0
- package/engine/evm/session.js +777 -0
- package/engine/mock/chain.js +98 -0
- package/engine/monero/address.js +265 -0
- package/engine/monero/halvesSweep.js +68 -0
- package/engine/monero/node.js +110 -0
- package/engine/monero/scan.js +325 -0
- package/engine/monero/swapKeys.js +69 -0
- package/engine/monero/unlock.js +46 -0
- package/engine/monero/wallet.js +168 -0
- package/engine/recovery/recoveryFile.js +363 -0
- package/engine/recovery/restore.js +176 -0
- package/engine/sdk/bridge.js +609 -0
- package/engine/ui/dom.js +90 -0
- package/engine/ui/walletModal.js +132 -0
- package/index.d.ts +1417 -0
- package/package.json +74 -0
- package/src/actions.mjs +72 -0
- package/src/adapters/evm-wallet.mjs +126 -0
- package/src/adapters/index.mjs +21 -0
- package/src/adapters/local-storage.mjs +32 -0
- package/src/adapters/monero-wallet.mjs +185 -0
- package/src/adapters/monero.mjs +51 -0
- package/src/adapters/node-test-evm-wallet.mjs +345 -0
- package/src/codes.mjs +188 -0
- package/src/config.mjs +277 -0
- package/src/engine.mjs +53 -0
- package/src/errors.mjs +24 -0
- package/src/http.mjs +72 -0
- package/src/index.mjs +256 -0
- package/src/legs/evm.mjs +118 -0
- package/src/legs/index.mjs +50 -0
- package/src/levelsWalkSpec.mjs +183 -0
- package/src/lock.mjs +140 -0
- package/src/order.mjs +212 -0
- package/src/preflight.mjs +137 -0
- package/src/primitives.mjs +47 -0
- package/src/quoteEip712.mjs +35 -0
- package/src/quoteEip712Spec.mjs +214 -0
- package/src/quotes.mjs +465 -0
- package/src/record.mjs +65 -0
- package/src/recovery.mjs +40 -0
- package/src/sim.mjs +37 -0
- package/src/swap-flow.mjs +492 -0
- package/src/swaps.mjs +935 -0
- package/src/sweep.mjs +222 -0
- package/src/wallet.mjs +164 -0
- package/src/xmr-arrival.mjs +289 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// GENERATED FILE - a byte-for-byte copy of the engine module www/js/core/chainSource.js for the SDK package.
|
|
2
|
+
// Edit the source under www/js, then run: node tools/build-sdk-engine.mjs
|
|
3
|
+
// tools/check-sdk-engine.mjs reddens on any drift, so a stale copy cannot ship silently.
|
|
4
|
+
// Everything below this header is identical to the source.
|
|
5
|
+
|
|
6
|
+
// Источник данных о Monero-ноге свопа: симулятор демки или настоящий бэкенд (app/).
|
|
7
|
+
//
|
|
8
|
+
// Почему отдельный модуль, а не строчки в core/swap.js: у свопа две ноги, и готовность у них разная.
|
|
9
|
+
//
|
|
10
|
+
// EVM-нога (escrow, claim, refund) - СИМУЛЯЦИЯ: контракта ещё нет (этап 5). Каждый её ответ
|
|
11
|
+
// помечен mock: true, и это не косметика, а то, что обязан показать UI.
|
|
12
|
+
//
|
|
13
|
+
// Monero-нога (пришёл ли XMR на адрес свопа и сколько у него подтверждений) - может быть
|
|
14
|
+
// НАСТОЯЩЕЙ: бэкенд app/ заводит watch-only кошелёк по адресу и view key и следит за
|
|
15
|
+
// поступлениями (см. app/README.md). Ключ траты при этом остаётся в браузере.
|
|
16
|
+
//
|
|
17
|
+
// Поэтому источник выбирается именно для Monero-ноги: 'mock' (как в демо: sim-время, без сети)
|
|
18
|
+
// либо 'app' (HTTP к бэкенду). За источником идёт и сеть: у бэкенда на сервере поднят
|
|
19
|
+
// stagenet-кошелёк, а мок-режим демки исторически mainnet (см. XMR_NETWORKS в config.js).
|
|
20
|
+
//
|
|
21
|
+
// ГРАНИЦА, ЗАКРЕПЛЁННАЯ КОДОМ: в бэкенд уходит только адрес и view key, и тело запроса
|
|
22
|
+
// собирается заново по белому списку полей - так в него физически не может уехать ни ключ траты,
|
|
23
|
+
// ни seed, даже если кто-то добавит их в share. Проверяется tools/check-chain-source.mjs.
|
|
24
|
+
|
|
25
|
+
import { API, XMR_NETWORKS, MONERO } from "./config.js";
|
|
26
|
+
import { confirmations as mockConfirmations } from "../mock/chain.js";
|
|
27
|
+
|
|
28
|
+
// Статусы бэкенда (app/store.mjs) - те же строки, что отдаёт API.
|
|
29
|
+
export const XMR_STATUS = {
|
|
30
|
+
AWAITING: "awaiting_funding", // на адрес ещё ничего не пришло (или пришло меньше ожидаемого)
|
|
31
|
+
FUNDED: "funded", // пришло, подтверждений меньше цели
|
|
32
|
+
// ДЕНЬГИ НА АДРЕСЕ ЕСТЬ, НО ЗАПЕРТЫ unlock_time (issue #84): это НЕ приход, и «готово» в этом состоянии не
|
|
33
|
+
// включается. Отдельный статус, а не молчание и не ноль: бэкенд (app/watcher.mjs) называет его сам.
|
|
34
|
+
LOCKED: "xmr_locked",
|
|
35
|
+
READY: "ready", // подтверждений достаточно: пользователь может подтверждать
|
|
36
|
+
SWEPT: "swept", // XMR выведены пользователем
|
|
37
|
+
CLOSED: "closed", // своп закрыт (отмена или возврат)
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Поля, которые вообще разрешено отправлять бэкенду при заведении свопа.
|
|
41
|
+
export const ALLOWED_SESSION_FIELDS = ["swapId", "network", "address", "viewKey", "expectedAmountXmr", "restoreHeight", "chain"];
|
|
42
|
+
|
|
43
|
+
export function isLiveSource(source = API.xmrSource) {
|
|
44
|
+
return source === "app";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Сеть Monero, которую обязан использовать адрес свопа при данном источнике.
|
|
48
|
+
export function xmrNetworkForSource(source = API.xmrSource) {
|
|
49
|
+
return XMR_NETWORKS[isLiveSource(source) ? "app" : "mock"];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function xmrSourceLabel(source = API.xmrSource) {
|
|
53
|
+
return isLiveSource(source) ? "backend app/ (stagenet)" : "demo simulator";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// HTTP к бэкенду
|
|
58
|
+
//
|
|
59
|
+
// Базу можно переопределить. В браузере это не нужно: "/api" - относительный путь того же
|
|
60
|
+
// origin, и CSP ("connect-src self") его покрывает. Переопределение существует для Node-тестов
|
|
61
|
+
// и для случая, когда фронт отдают не с боевого хоста (в обоих случаях относительный URL не парсится).
|
|
62
|
+
let apiBaseOverride = null;
|
|
63
|
+
export function setApiBase(base) {
|
|
64
|
+
apiBaseOverride = base ? String(base).replace(/[/]$/, "") : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Настройки опроса, которые можно переопределить: нужно тестам (там интервал в миллисекундах,
|
|
68
|
+
// а не в секундах) и настройке экрана. Значения по умолчанию - из config.js.
|
|
69
|
+
let pollMsOverride = null;
|
|
70
|
+
export function setApiOptions({ pollMs } = {}) {
|
|
71
|
+
if (Number.isFinite(Number(pollMs)) && Number(pollMs) > 0) pollMsOverride = Number(pollMs);
|
|
72
|
+
return { pollMs: pollMsOverride || "default" };
|
|
73
|
+
}
|
|
74
|
+
export function pollInterval() {
|
|
75
|
+
return pollMsOverride || API.pollMs;
|
|
76
|
+
}
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
// Один запрос к API. НИКОГДА не бросает: упавшая сеть - это состояние экрана, а не падение демки,
|
|
80
|
+
// и пользователь должен увидеть причину, а не пустую страницу.
|
|
81
|
+
async function api(path, { method = "GET", body, base, timeoutMs = API.timeoutMs, fetchImpl } = {}) {
|
|
82
|
+
const target = base || apiBaseOverride || API.base;
|
|
83
|
+
const doFetch = fetchImpl || globalThis.fetch;
|
|
84
|
+
if (typeof doFetch !== "function") return { ok: false, status: 0, error: "нет fetch: бэкенд недоступен" };
|
|
85
|
+
const ctl = typeof AbortController === "function" ? new AbortController() : null;
|
|
86
|
+
const timer = ctl ? setTimeout(() => ctl.abort(), timeoutMs) : null;
|
|
87
|
+
try {
|
|
88
|
+
const res = await doFetch(target + path, {
|
|
89
|
+
method,
|
|
90
|
+
headers: body ? { "Content-Type": "application/json" } : undefined,
|
|
91
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
92
|
+
signal: ctl ? ctl.signal : undefined,
|
|
93
|
+
});
|
|
94
|
+
let text = "";
|
|
95
|
+
try {
|
|
96
|
+
text = await res.text();
|
|
97
|
+
} catch {
|
|
98
|
+
text = "";
|
|
99
|
+
}
|
|
100
|
+
let json = null;
|
|
101
|
+
try {
|
|
102
|
+
json = text ? JSON.parse(text) : null;
|
|
103
|
+
} catch {
|
|
104
|
+
/* не JSON - причину увидим из текста */
|
|
105
|
+
}
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const base_ = (json && (json.error || json.detail)) || text.slice(0, 200) || `HTTP ${res.status}`;
|
|
108
|
+
const extra = json && Array.isArray(json.problems) ? ": " + json.problems.join("; ") : "";
|
|
109
|
+
return { ok: false, status: res.status, error: `${base_}${extra}` };
|
|
110
|
+
}
|
|
111
|
+
return { ok: true, status: res.status, json };
|
|
112
|
+
} catch (e) {
|
|
113
|
+
const aborted = e && e.name === "AbortError";
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
status: 0,
|
|
117
|
+
error: aborted ? `бэкенд не ответил за ${timeoutMs} мс` : "бэкенд недоступен: " + ((e && e.message) || e),
|
|
118
|
+
};
|
|
119
|
+
} finally {
|
|
120
|
+
if (timer) clearTimeout(timer);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Ответ бэкенда -> слепок для экрана и движка состояний.
|
|
125
|
+
export function normalizeSession(json) {
|
|
126
|
+
const j = json || {};
|
|
127
|
+
return {
|
|
128
|
+
id: j.id || null,
|
|
129
|
+
status: j.status || XMR_STATUS.AWAITING,
|
|
130
|
+
network: j.network || null,
|
|
131
|
+
address: j.address || null,
|
|
132
|
+
expectedAmountXmr: j.expectedAmountXmr ?? null,
|
|
133
|
+
receivedXmr: j.receivedXmr ?? "0",
|
|
134
|
+
receivedAtomic: j.receivedAtomic ?? "0",
|
|
135
|
+
// ЗАПЕРТОЕ unlock_time: сумма и граница ({code,untilHeight,untilTime}) - отдельно от прихода.
|
|
136
|
+
lockedXmr: j.lockedXmr ?? "0",
|
|
137
|
+
lockedAtomic: j.lockedAtomic ?? "0",
|
|
138
|
+
lockedUntil: j.lockedUntil || null,
|
|
139
|
+
confirmations: Number(j.confirmations) || 0,
|
|
140
|
+
fundingTxids: Array.isArray(j.fundingTxids) ? j.fundingTxids : [],
|
|
141
|
+
nodeHeightAtSeen: j.nodeHeightAtSeen ?? null,
|
|
142
|
+
sweepTxid: j.sweepTxid || null,
|
|
143
|
+
updatedAt: j.updatedAt || null,
|
|
144
|
+
at: Date.now(),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Заведение свопа на бэкенде. share - то, что возвращает watchOnlyShareFromWallet()
|
|
149
|
+
// (recovery/restore.js): адрес + приватный view key. Ключ траты остаётся в браузере.
|
|
150
|
+
// Одна и та же ли цель у свопа: сумма, валюта и сеть. Нужно, чтобы клик по Swap не заводил новый своп
|
|
151
|
+
// на тот же адрес, если человек ничего не менял. Чистая функция - проверяется тестом без сети.
|
|
152
|
+
export function sameSwapTarget(a, b) {
|
|
153
|
+
if (!a || !b) return false;
|
|
154
|
+
return String(a.payAmount) === String(b.payAmount) &&
|
|
155
|
+
a.payToken === b.payToken &&
|
|
156
|
+
a.chain === b.chain;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function openSession({ swapId, share, expectedAmountXmr, chain, restoreHeight }, opts = {}) {
|
|
160
|
+
const src = share || {};
|
|
161
|
+
const address = src.address || null;
|
|
162
|
+
const viewKey = src.viewKey || src.privateViewKey || null;
|
|
163
|
+
if (!address || !viewKey) {
|
|
164
|
+
return { ok: false, status: 0, error: "в watch-only share нет address/viewKey - нечего отправлять бэкенду" };
|
|
165
|
+
}
|
|
166
|
+
// СУММА ОЖИДАНИЯ ОБЯЗАТЕЛЬНА и должна быть числом. Раньше здесь стояло «не число - значит поля не будет»,
|
|
167
|
+
// и своп заводился БЕЗ суммы: сторож принимал за достаточное любое поступление, то есть недоплата
|
|
168
|
+
// выглядела как исполненная сделка. Живой своп так и остался с пустым полем. Теперь это отказ с
|
|
169
|
+
// объяснением: не завести наблюдение лучше, чем завести его без возможности проверить деньги.
|
|
170
|
+
const amountXmr = Number(expectedAmountXmr);
|
|
171
|
+
if (!Number.isFinite(amountXmr) || amountXmr <= 0) {
|
|
172
|
+
const where = (new Error("caller").stack || "").split("\n").slice(1, 4).map((x) => x.trim().split("/").pop()).join(" <- ");
|
|
173
|
+
console.warn("[chainSource] нет суммы ожидания, вызов из: " + where);
|
|
174
|
+
return { ok: false, status: 0, error: "сумма ожидания XMR неизвестна (" + String(expectedAmountXmr) + "): без неё наблюдение не заводится" };
|
|
175
|
+
}
|
|
176
|
+
const wanted = {
|
|
177
|
+
swapId: swapId || src.swapId || null,
|
|
178
|
+
network: src.network || xmrNetworkForSource(),
|
|
179
|
+
address,
|
|
180
|
+
viewKey,
|
|
181
|
+
expectedAmountXmr: amountXmr,
|
|
182
|
+
restoreHeight: Number.isFinite(Number(restoreHeight)) && Number(restoreHeight) > 0 ? Number(restoreHeight) : Number(src.restoreHeight) || undefined,
|
|
183
|
+
chain: chain || undefined,
|
|
184
|
+
};
|
|
185
|
+
// Тело собираем заново по белому списку: что не в списке - в запрос не попадёт никогда.
|
|
186
|
+
const body = {};
|
|
187
|
+
for (const field of ALLOWED_SESSION_FIELDS) {
|
|
188
|
+
if (wanted[field] !== undefined && wanted[field] !== null) body[field] = wanted[field];
|
|
189
|
+
}
|
|
190
|
+
const res = await api("/swaps", { method: "POST", body, ...opts });
|
|
191
|
+
if (!res.ok) return res;
|
|
192
|
+
return { ok: true, status: res.status, created: res.json?.created !== false, session: normalizeSession(res.json) };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function fetchSession(swapId, opts = {}) {
|
|
196
|
+
const res = await api(`/swaps/${encodeURIComponent(swapId)}`, opts);
|
|
197
|
+
if (!res.ok) return res;
|
|
198
|
+
return { ok: true, status: res.status, session: normalizeSession(res.json) };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Сообщить бэкенду, что XMR выведены: хеш настоящего перевода (со страницы sweep).
|
|
202
|
+
// Нужно потому, что watch-only кошелёк исходящие переводы видеть не может в принципе.
|
|
203
|
+
export async function reportSwept(swapId, txid, opts = {}) {
|
|
204
|
+
const res = await api(`/swaps/${encodeURIComponent(swapId)}/swept`, { method: "POST", body: { txid }, ...opts });
|
|
205
|
+
return res.ok ? { ok: true, status: res.status, session: normalizeSession(res.json) } : res;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Закрыть своп (отмена или возврат на стороне бэкенда): наблюдение прекращается.
|
|
209
|
+
export async function closeSession(swapId, reason = "cancelled", opts = {}) {
|
|
210
|
+
const res = await api(`/swaps/${encodeURIComponent(swapId)}/closed`, { method: "POST", body: { reason }, ...opts });
|
|
211
|
+
return res.ok ? { ok: true, status: res.status, session: normalizeSession(res.json) } : res;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Состояние бэкенда: доступен ли он и видит ли кошелёк. Нужно экрану, чтобы показать «бэкенд
|
|
215
|
+
// недоступен» до того, как пользователь отправит деньги, а не после.
|
|
216
|
+
export async function health(opts = {}) {
|
|
217
|
+
const res = await api("/health", opts);
|
|
218
|
+
return res.ok ? { ok: true, ...res.json } : res;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Слепок Monero-ноги для движка состояний
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
// Мок-режим: подтверждения по sim-времени, ровно как в демке было до сих пор.
|
|
226
|
+
export function mockView(swap, simElapsedMs) {
|
|
227
|
+
const confTarget = (swap.timeline && swap.timeline.confTarget) || MONERO.confirmTarget;
|
|
228
|
+
const lockAt = swap.timeline ? swap.timeline.lockAtSim : 0;
|
|
229
|
+
const locked = lockAt !== null && simElapsedMs >= lockAt;
|
|
230
|
+
const confirmations = mockConfirmations(swap, simElapsedMs);
|
|
231
|
+
const status = !locked ? XMR_STATUS.AWAITING : confirmations >= confTarget ? XMR_STATUS.READY : XMR_STATUS.FUNDED;
|
|
232
|
+
// WHAT ACTUALLY ARRIVED. By default the quoted amount: the demo path without underpayment. If the record names
|
|
233
|
+
// the amount that REALLY arrived (swap.xmr.received), show that: it is exactly what an underpayment looks like -
|
|
234
|
+
// the shortfall that sends the order into a refund - and the screen must name BOTH numbers, not repeat one twice.
|
|
235
|
+
const stated = Number(swap.xmr && swap.xmr.received);
|
|
236
|
+
const received = locked ? (Number.isFinite(stated) ? stated : swap.xmrAmount) : 0;
|
|
237
|
+
return {
|
|
238
|
+
live: false,
|
|
239
|
+
status,
|
|
240
|
+
received,
|
|
241
|
+
// В демо-симуляторе запирания unlock_time нет: приход и разблокированное - одно и то же.
|
|
242
|
+
unlocked: received,
|
|
243
|
+
locked: 0,
|
|
244
|
+
lockedUntil: null,
|
|
245
|
+
arrived: received,
|
|
246
|
+
confirmations,
|
|
247
|
+
confTarget,
|
|
248
|
+
txids: swap.xmr && swap.xmr.txid ? [swap.xmr.txid] : [],
|
|
249
|
+
address: (swap.xmr && swap.xmr.address) || null,
|
|
250
|
+
sweepTxid: (swap.xmr && swap.xmr.sweepTxid) || null,
|
|
251
|
+
error: null,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Живой режим: берём последний опрос бэкенда. Его кладёт тикер (swap.js), а derive() остаётся
|
|
256
|
+
// чистой функцией - она читает состояние, а не ходит в сеть.
|
|
257
|
+
export function liveView(swap) {
|
|
258
|
+
const confTarget = (swap.timeline && swap.timeline.confTarget) || MONERO.confirmTarget;
|
|
259
|
+
const s = swap.xmrSession;
|
|
260
|
+
if (!s) {
|
|
261
|
+
return {
|
|
262
|
+
live: true,
|
|
263
|
+
status: swap.xmrSessionError ? "error" : "opening",
|
|
264
|
+
received: 0,
|
|
265
|
+
unlocked: 0,
|
|
266
|
+
locked: 0,
|
|
267
|
+
lockedUntil: null,
|
|
268
|
+
arrived: 0,
|
|
269
|
+
confirmations: 0,
|
|
270
|
+
confTarget,
|
|
271
|
+
txids: [],
|
|
272
|
+
address: (swap.swapWallet && swap.swapWallet.address) || null,
|
|
273
|
+
sweepTxid: null,
|
|
274
|
+
error: swap.xmrSessionError || null,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
live: true,
|
|
279
|
+
status: swap.xmrSessionError ? "error" : s.status,
|
|
280
|
+
// ПРИХОД И РАЗБЛОКИРОВАННОЕ - РАЗНЫЕ ФАКТЫ. received - то, что можно потратить (receivedXmr),
|
|
281
|
+
// locked - запертое unlock_time, arrived - всё, что видно на адресе. Состояние называет бэкенд.
|
|
282
|
+
received: Number(s.receivedXmr) || 0,
|
|
283
|
+
unlocked: Number(s.receivedXmr) || 0,
|
|
284
|
+
locked: Number(s.lockedXmr) || 0,
|
|
285
|
+
lockedUntil: s.lockedUntil || null,
|
|
286
|
+
arrived: (Number(s.receivedXmr) || 0) + (Number(s.lockedXmr) || 0),
|
|
287
|
+
confirmations: s.confirmations,
|
|
288
|
+
confTarget,
|
|
289
|
+
txids: s.fundingTxids,
|
|
290
|
+
address: s.address || (swap.swapWallet && swap.swapWallet.address) || null,
|
|
291
|
+
sweepTxid: s.sweepTxid,
|
|
292
|
+
error: swap.xmrSessionError || null,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function xmrView(swap, simElapsedMs) {
|
|
297
|
+
return isLiveSource(swap && swap.xmrSource) ? liveView(swap) : mockView(swap, simElapsedMs);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Ссылка на перевод в обозревателе. В live-режиме это stagenet: подписывать её как настоящие
|
|
301
|
+
// деньги нельзя, поэтому сеть берём из самой сессии, а не из догадки.
|
|
302
|
+
export function explorerXmrTx(txid, network = "stagenet") {
|
|
303
|
+
if (!txid) return null;
|
|
304
|
+
const prefix = network === "mainnet" ? "" : network + ".";
|
|
305
|
+
return `https://${prefix}xmrchain.net/tx/${txid}`;
|
|
306
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// GENERATED FILE - a byte-for-byte copy of the engine module www/js/core/clock.js for the SDK package.
|
|
2
|
+
// Edit the source under www/js, then run: node tools/build-sdk-engine.mjs
|
|
3
|
+
// tools/check-sdk-engine.mjs reddens on any drift, so a stale copy cannot ship silently.
|
|
4
|
+
// Everything below this header is identical to the source.
|
|
5
|
+
|
|
6
|
+
// Sim-часы демки.
|
|
7
|
+
//
|
|
8
|
+
// Идея: реальное время сжимается множителем speed. speed=60 означает, что одна
|
|
9
|
+
// реальная секунда равна одной sim-минуте. Все тайминги сделки живут в sim-времени,
|
|
10
|
+
// поэтому демку можно показывать быстро, а на speed=1 она идёт в реальном темпе
|
|
11
|
+
// (20-40 минут на сделку, как в литпепере).
|
|
12
|
+
//
|
|
13
|
+
// swap.realStart - Date.now() в момент создания сделки
|
|
14
|
+
// swap.simStartWall - настенное время демки в этот же момент (для отображения UTC)
|
|
15
|
+
// simElapsed(swap) = (Date.now() - realStart) * speed
|
|
16
|
+
|
|
17
|
+
export function nowReal() {
|
|
18
|
+
return Date.now();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function simElapsed(swap, now = nowReal()) {
|
|
22
|
+
return Math.max(0, (now - swap.realStart) * swap.speed);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Настенное время демки в момент, когда прошло simMs sim-времени с начала сделки.
|
|
26
|
+
export function simWall(swap, simMs) {
|
|
27
|
+
return swap.simStartWall + simMs;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// Пересчёт базовой точки при смене скорости: sim-прогресс сохраняется,
|
|
33
|
+
// дальше сделка идёт с новой скоростью.
|
|
34
|
+
export function rescaleStart(swap, newSpeed, now = nowReal()) {
|
|
35
|
+
const elapsed = simElapsed(swap, now);
|
|
36
|
+
swap.speed = newSpeed;
|
|
37
|
+
swap.realStart = now - elapsed / newSpeed;
|
|
38
|
+
return swap;
|
|
39
|
+
}
|
|
40
|
+
|