@dusk-network/app-core 0.3.2 → 0.4.1

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 CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  All notable changes to this package are listed here. It follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/).
4
4
 
5
+ ## [0.4.1]
6
+
7
+ ### Changed
8
+
9
+ - `/duskevm-withdrawals`: a withdrawal's receipt is kept in `cache` once its DuskEVM block is final, and read once
10
+
11
+ ### Fixed
12
+
13
+ - `/bridge`: a memo of bytes that aren't text (`memoBinary`) gives no payout address, and names no migration
14
+
15
+ ## [0.4.0]
16
+
17
+ ### Added
18
+
19
+ - `/history`: `memoBinary` on an entry whose memo is bytes given as 0x hex, rather than typed 0x text
20
+ - `/history`: a `check` for the store's `patch` and `clear` to make in the same transaction, so a run after a reset leaves another run's history of the new chain alone
21
+ - `/shielded`: an optional `update` on the store, which keeps the decoys in one transaction, so two reads at once ask among the same ones
22
+ - `/bridge`: `serviceArrivals` takes `blockTimes` as a cache for each chain, by its chain ID
23
+
24
+ ### Changed
25
+
26
+ - `/history`: a details request that fails in `sync()` ends the run's details and resolves it with `detailsError`, rather than rejecting the run
27
+ - `/bridge`: an RPC search's windows grow up to 100,000 blocks while the RPC answers them whole, and a call reads a chain's head and pace once for all its searches
28
+
29
+ ### Fixed
30
+
31
+ - `/shielded`: a conversion is a shield or an unshield as its event says, also for a profile that only got its change
32
+ - `/shielded`: two notes a block made for one shielded address go to a transaction each, rather than both to the first
33
+ - `/activity`: a gas refund from another account's call to DuskEVM's bridge is a received transfer, rather than a bridge withdrawal
34
+ - `/bridge`: an RPC search starts 10 minutes before the transfer as the chain's pace says, at a block whose time is checked to be before it, rather than a fifth further back than the pace says
35
+ - `/bridge`: an RPC search keeps the width of its windows in its `scan`, so one window a call moves on past an RPC that takes fewer blocks than it asked
36
+ - `/bridge`: `serviceArrivals` uses one `blockTimes` cache only for a network whose service routes are all on one chain
37
+ - `/bridge`: a deposit without its sender's history reads no further once its windows are over and read
38
+ - `/duskevm-withdrawals`: `syncWithdrawals` reads the final blocks again, the newest 50,000 on each run and all of them a pass at a time once covered, rather than once
39
+ - `/duskevm-withdrawals`: `syncWithdrawals` counts both its head's requests against `maxRequests` where the RPC has no finalized block
40
+
5
41
  ## [0.3.2]
6
42
 
7
43
  ### Fixed
package/README.md CHANGED
@@ -53,13 +53,14 @@ const { entries, complete } = await history.read({ limit: 50 });
53
53
  ```
54
54
 
55
55
  - A run reads the newest heights first, in windows under the last finalized block: a day, then wider while they hold few entries, narrower where a page of 50 fills. It makes at most 8 history requests and 2 details requests (`maxRequests`, 2 at least, and `maxDetailRequests`), and keeps each step as it lands, each page of a block with more than a page too, so the next run carries on where it stopped. A later run asks only for what is above the heights kept.
56
+ - A details request that fails ends the run's details, not the run: it reads on, and resolves with `detailsError`. The next run asks for the details again. A history request that fails rejects the run, as does the store.
56
57
  - `read({ limit, before })` gives the entries above the first gap, newest first: below it, newer ones may be missing. `complete` says every height from the head down is covered. When `limit` cuts through a block, the rest of that block comes too, so `before` the last entry's height reads on without skipping any.
57
58
  - A node whose GraphQL has no archive is asked nothing for an hour. Without `refusals`, the archive object alone remembers it.
58
- - A node on another chain than the history kept (a reset devnet) makes the run ask the store to `clear()` and start over, within its budget. A run another one overtook that way, or whose history was cleared under it, keeps nothing more and rejects with a `StaleHistoryError`.
59
+ - A node on another chain than the history kept (a reset devnet) makes the run ask the store to `clear()` and start over, within its budget; where another run started it over on that chain already, it carries on from what that run kept. A run another one overtook that way, or whose history was cleared under it, keeps nothing more and rejects with a `StaleHistoryError`.
59
60
  - `network` is the node's network or its chain ID, for the DuskEVM bridge's calls, the bridge's routes and Sozu. A node of a network there's no file for passes none.
60
61
  - `fillDetails(entries)` reads the details of entries shown before a run gave them any.
61
62
 
62
- An entry is facts, not text: `direction` ("in", "out", "self"), `kind` (`transfer`, `shield`, `unshield`, `contract_call`, `stake`, `unstake`, `withdraw_reward`), `counterparty`, `value` and `fee` in lux (a gas refund paid to another account counts in the sender's `value` and in that account's, told apart as `refund`), `memo`, and with its details its time, outcome, gas and call. A call to DuskEVM's bridge or Portal has `bridge`, what it did (`decodeBridgeCall`): a deposit's amount and DuskEVM address, or a withdrawal's proof or claim, its hash, whom it pays and its nonce. A transfer with one of the bridge's accounts has its `route` (`routeForAccount`), and a stake or an unstake with Sozu `sozu: true` (`isSozuCall`, with the pools the hub named in `read({ pools })`).
63
+ An entry is facts, not text: `direction` ("in", "out", "self"), `kind` (`transfer`, `shield`, `unshield`, `contract_call`, `stake`, `unstake`, `withdraw_reward`), `counterparty`, `value` and `fee` in lux (a gas refund paid to another account counts in the sender's `value` and in that account's, told apart as `refund`), `memo` (as text, or as 0x hex with `memoBinary: true` for bytes that aren't text), and with its details its time, outcome, gas and call. A call to DuskEVM's bridge or Portal has `bridge`, what it did (`decodeBridgeCall`): a deposit's amount and DuskEVM address, or a withdrawal's proof or claim, its hash, whom it pays and its nonce. A transfer with one of the bridge's accounts has its `route` (`routeForAccount`), and a stake or an unstake with Sozu `sozu: true` (`isSozuCall`, with the pools the hub named in `read({ pools })`).
63
64
 
64
65
  ### The store
65
66
 
@@ -69,9 +70,11 @@ The app keeps each account's history where it wants, under its own keys, and pas
69
70
  |---|---|
70
71
  | `getState()` | The sync state, or null before the first run. |
71
72
  | `commit(entries, update)` | Keeps one step in one transaction: `entries` by hash, an entry kept already keeping the fields these don't carry (its details), and the state `update(kept)` makes from the state kept now. When `update` throws, nothing of the step is kept. Gives the state kept. |
72
- | `patch(details)` | Adds details to kept entries, by hash (a `Map`); entries that aren't kept are left alone. |
73
+ | `patch(details, check)` | Adds details to kept entries, by hash (a `Map`); entries that aren't kept are left alone. |
73
74
  | `list({ limit, before })` | Kept entries, newest first: at most `limit`, below the height `before` when given. |
74
- | `clear()` | Forgets what is kept of the old chain. |
75
+ | `clear(check)` | Forgets what is kept of the old chain. |
76
+
77
+ A store that can calls `check(kept)` first, with the state kept now, in the same transaction, and writes nothing when it throws. Then no details go on a history another run started over since, and a run that finds a reset doesn't forget what another one already read of the new chain. A store that doesn't call it works as before.
75
78
 
76
79
  The state is `{ account, covered, crowded, finalized, genesis, headAt, target, updatedAt, window }`; fields the store keeps beside these stay. An entry is kept with its amounts as decimal strings, so it stores as JSON. A store that rejects a write, as one bound to a wallet that has been reset since, makes the run reject, and nothing more is kept.
77
80
 
@@ -130,7 +133,7 @@ const { entries, pending } = await shieldedHistory({
130
133
  - `isCurrent` is asked between every two steps, before each request and write, and once more after the history is kept: once the wallet, the profile or the node changed, the read asks and keeps nothing more and rejects. A node that answered without the archive is asked nothing for an hour, its tree included.
131
134
  - The blocks come 15 heights a request, through the archive of `/history`.
132
135
 
133
- An entry is facts, not text: `kind` (the kinds of `/history`), `direction` for the profile's notes ("in", "out", or "self" when what they spent came back but the fee), `value` and `fee` in lux as decimal strings (a fee only where the profile's notes paid it), `success` and `error`, and its block's `timestamp`. It stores as JSON as it is.
136
+ An entry is facts, not text: `kind` (the kinds of `/history`; a conversion's by where its value went, as its event says), `direction` for the profile's notes ("in", "out", or "self" when what they spent came back but the fee), `value` and `fee` in lux as decimal strings (a fee only where the profile's notes paid it), `success` and `error`, and its block's `timestamp`. It stores as JSON as it is.
134
137
 
135
138
  ### The stores and the tree
136
139
 
@@ -140,6 +143,7 @@ The app keeps the history where it wants, under its own keys, and reads its note
140
143
  |---|---|
141
144
  | `store.get()` | The profile's kept history, `{ fingerprint, entries, decoys }`, or null. Reject when it can't be read: the read then asks nothing rather than draw new decoys. |
142
145
  | `store.put(record)` | Keeps it in place of the one kept. A put that rejects, as one bound to a wallet that has been reset since, rejects the read. A store that can't keep a history for want of room may pass over it: the read gives it anyway, and the next one works it out again among the same decoys. |
146
+ | `store.update(change)` | Optional: keeps `change(kept)` in place of the record kept now, in one transaction, and gives what it kept. The decoys are then drawn from those kept and kept at once, so two reads of a profile at once ask among the same ones. Without it, two such reads can each draw their own. It rejects and passes over a history as `put` does. |
143
147
  | `treeHeights.get()`, `treeHeights.put({ heights, notes })` | The note tree's distinct heights from its first `notes` leaves, shared by the network's profiles. A put that fails is passed over. |
144
148
  | `tree.size()` | How many leaves the node's note tree has (the transfer contract's `num_notes`). |
145
149
  | `tree.leaves(from)` | Its leaves from position `from` on (`leaves_from_pos`): a stream of their bytes, or an answer whose body is one. |
@@ -167,7 +171,7 @@ const rows = activityRows({
167
171
  - A send the chain's histories don't have yet is `pending`: for 15 minutes at most (`PENDING_MS`), unless the node has it in its queue, then `unknown`. One the node dropped is `unknown` and `dropped`. A send marked settled shows with the chain's row alone.
168
172
  - The shielded side of a shield or an unshield is the public row's, with the fee the shielded side paid.
169
173
  - `category` is the filter a row is under: "staking" (native and Sozu's), "bridge" (DuskEVM's bridge and the routes to other chains), "sent", "received", or "other" (a shield, an unshield, a transfer to the account itself).
170
- - A call to DuskEVM's bridge or Portal has `bridge`: what it did (`step`: "deposit", "withdrawal", "proof", "claim" or "call"), the amount its call names, a deposit's DuskEVM address and where it arrived there (`arrivals`), a withdrawal's hash, recipient and nonce, and whether a proof's withdrawal is claimed: by a claim of the same activity that went through, is on its way or is on chain, or as the app read it (`claims`).
174
+ - A call to DuskEVM's bridge or Portal has `bridge`: what it did (`step`: "deposit", "withdrawal", "proof", "claim" or "call"), the amount its call names, a deposit's DuskEVM address and where it arrived there (`arrivals`), a withdrawal's hash, recipient and nonce, and whether a proof's withdrawal is claimed: by a claim of the same activity that went through, is on its way or is on chain, or as the app read it (`claims`). What came in from an account has none, whatever its transaction called: a gas refund from another account's call to the bridge is a transfer from that account.
171
175
  - A transfer with one of the bridge's accounts has its `route`, and a call of Sozu's pool `sozu`, "stake" or "unstake" (with the pools the hub named in `pools`).
172
176
  - `nameCall` is the app's hook for the name service, as the wallets read .dusk names with `@duskdomains/sdk/call-facts`: given a call's contract and function, and the entry or the send it is from, it says what the call does, and the name and the reservation it is for. A reservation has the name of a registration after it with the same reservation.
173
177
 
@@ -202,11 +206,11 @@ arrivalsOf(arrivals); // Map { hash => { hash: "0x029e…", at: 1793080333000 }
202
206
  - A transfer's time is its Dusk block's, never the device's clock. One without it yet isn't looked for. One sent more than a week ago (`waitMs`) is unknown, and isn't asked about.
203
207
  - A deposit is followed on DuskEVM's explorer (`via: "explorer"`, the default): the recipient's incoming calls, newest first, read once for all its deposits, back past the oldest, 5 pages at most. Those calls don't say who deposited, so a payment is a deposit's only when it can't be another's: within 2 minutes either side of its block, once those minutes are over, with no other payment of its amount there (a failed one counts) that isn't another given deposit's. Nothing is asked before then.
204
208
  - On DuskEVM's RPC (`via: "rpc"`), a deposit is found by DuskEVM's bridge event from its sender's DuskEVM address. With `/history`'s archive, each sender's equal deposits in the hours before its own are read from its history and paid first; a deposit there whose call can't be read leaves the order unclear. Without it, the RPC's credits are taken as the explorer's are. A deposit given without its sender takes no credit from the sender of another equal one given.
205
- - A migration is paid by a transfer to the account from its route's payout account, with the migration's transaction hash as the memo, of exactly its amount: from the account's entries, less any `refund` paid to the account with it, or from the archive's memo index, which gives each payout's own amount. One from that account of another amount leaves it unsure, not waiting: the value of an entry an earlier version kept holds any gas refund paid to the account. With `entries`, the payouts there of migrations the app didn't list come too (`unlisted`), each with its `payer`; where one account pays out more than one route, a payout of a migration not listed doesn't say which: its `route` is null.
206
- - A transfer out is read from the chain: what the route's account received, the address its memo gives as the route's bridge reads it (0x and 40 hex digits, any case, nothing around them), and its block. It arrives with the DUSK token's `Transfer` from its route's payout address to that address, of what the account received less the fee. The account's history lists every transfer that named the same address, in any case: equal ones in the hours before (a given one's by its own fee) are paid first, and one that can't be read leaves the order unclear. One that pays nothing out is `no-payout`, with its `problem`. `serviceTransferOf` gives what left the account (`sent`), and what the account received (`amount`) only where no gas was left to refund: an entry's value holds any refund paid to another account.
209
+ - A migration is paid by a transfer to the account from its route's payout account, with the migration's transaction hash as the memo, as text, of exactly its amount: from the account's entries, less any `refund` paid to the account with it, or from the archive's memo index, which gives each payout's own amount. One from that account of another amount leaves it unsure, not waiting: the value of an entry an earlier version kept holds any gas refund paid to the account. With `entries`, the payouts there of migrations the app didn't list come too (`unlisted`), each with its `payer`; where one account pays out more than one route, a payout of a migration not listed doesn't say which: its `route` is null.
210
+ - A transfer out is read from the chain: what the route's account received, the address its memo gives as the route's bridge reads it (0x and 40 hex digits, any case, nothing around them, as text: a memo of bytes that aren't text gives none), and its block. It arrives with the DUSK token's `Transfer` from its route's payout address to that address, of what the account received less the fee. The account's history lists every transfer that named the same address, in any case: equal ones in the hours before (a given one's by its own fee) are paid first, and one that can't be read leaves the order unclear. One that pays nothing out is `no-payout`, with its `problem`. `serviceTransferOf` gives what left the account (`sent`), and what the account received (`amount`) only where no gas was left to refund: an entry's value holds any refund paid to another account.
207
211
  - With the archive, each transfer reads only its own hours of its account's history (its sender's for a deposit, the route's account's for a transfer out), further back while equal ones before it run on, and only once the archive lists the transfer there. A call reads 4 pages of it (`HISTORY_PAGES`) for each: a transfer whose history is longer waits, and the next call reads on from its `scan`.
208
212
  - A route without its payout account or address in the network's file pays out nothing that can be told apart: `no-route`.
209
- - An RPC search starts at a block from before the transfer, estimated from the chain's pace and checked by its time, and reads 8 windows of 5,000 blocks at most, narrower where the RPC asks for it. It reads each block more than once: its window at the head reaches back 1,000 blocks, so the newest blocks are read again on each call, and once it has reached the head with a transfer not arrived, it reads its blocks again from the first, a pass at a time with the windows a call has left (one at least while a pass is under way, so 9 where the search ahead takes all 8), in windows no wider than the RPC took: the first a minute later, each one after as long again as it has been since it reached the head. Where a later equal transfer, still without its credit, could have had the credit an earlier one takes, the earlier one waits until the blocks are read again 8 times since the credit was found, one pass after the other. A result that isn't arrived says where the next search starts, with what it searched for and the credits found so far (`scan`): the app keeps it with the arrivals it was made with, and passes both back. A search for another sender or amount starts afresh.
213
+ - An RPC search starts at a block from before the transfer, 10 minutes before it as the chain's pace says, checked by its time and stepped back until it is, and reads 8 windows at most: of 5,000 blocks, twice as many after each one the RPC answers whole, up to 100,000, and fewer where it asks for it, the width kept in the search's `scan` from call to call. A call reads a chain's head and pace once for all its searches. Once a deposit's windows are over and read, a search without its sender's history reads no further. It reads each block more than once: its window at the head reaches back 1,000 blocks, so the newest blocks are read again on each call, and once it has reached the head with a transfer not arrived, it reads its blocks again from the first, a pass at a time with the windows a call has left (one at least while a pass is under way, so 9 where the search ahead takes all 8): the first a minute later, each one after as long again as it has been since it reached the head. Where a later equal transfer, still without its credit, could have had the credit an earlier one takes, the earlier one waits until the blocks are read again 8 times since the credit was found, one pass after the other. A result that isn't arrived says where the next search starts, with what it searched for and the credits found so far (`scan`): the app keeps it with the arrivals it was made with, and passes both back. A search for another sender or amount starts afresh. The block times the app keeps (`blockTimes`) are a chain's: for transfers out, a cache a chain by its chain ID, or one cache where the network's service routes are all on one chain.
210
214
 
211
215
  A result is facts: `arrived`, with the other side's transaction, its block's time, the lux it paid and its block; `waiting`; or `unknown`, with why: `unsure` (a credit could be another's), `late` (none came in its window), `beyond` (the explorer's pages don't reach it), `searching` (carry on from `scan`), `expired`, `no-route`, `no-payout` or `error`. The words are the app's.
212
216
 
@@ -248,13 +252,13 @@ const claimCall = await withdrawals.claimCall(txHash, { submitter: account }); /
248
252
 
249
253
  ### Finding withdrawals
250
254
 
251
- - `syncWithdrawals({ address, duskEvm, network, state })` reads an address's withdrawals from the bridge's own `ETHBridgeInitiated` events, the newest final blocks first, a window of 100,000 blocks at a time (narrower where the RPC asks for it), 8 requests a run. It gives the app the state to keep, and the next run carries on below and reads only what is new above. Under a final head lower than the last run's, what it covered and found above it is dropped, and read again.
255
+ - `syncWithdrawals({ address, duskEvm, network, state })` reads an address's withdrawals from the bridge's own `ETHBridgeInitiated` events, the newest final blocks first, a window of 100,000 blocks at a time (narrower where the RPC asks for it), 8 requests a run, the head's among them. It gives the app the state to keep, and the next run carries on below. Each run reads the newest 50,000 final blocks, the new ones first, in as many windows as they take, though in half its requests at most (while older blocks are still to be read, only once the final head has moved). Under a final head lower than the last run's, what it covered and found above it is dropped, and read again. It reads each final block more than once: once it has covered them all, it reads them again from the newest down, a pass at a time with the requests a run has left, the first an hour later and each one after as long again as it has been since. A withdrawal is found once.
252
256
  - `explorerWithdrawals({ address, network, fetch })` lists the address's own calls of the bridge on DuskEVM's explorer, each with the explorer's word for it (`explorerStage`), and `withdrawalsByNonce({ nonces, network, fetch })` finds the DuskEVM transactions of withdrawals known by the nonces their proofs and claims on Dusk carry.
253
257
 
254
258
  ### Reads and caches
255
259
 
256
260
  - The Portal and the dispute game factory are read with the app's drivers; each game's hub and the anchor state registry raw. Reads go to the node's contracts through the app's `fetch` (its Multicall for Dusk batcher), sent again twice at most when the node answers 429, 502, 503 or 504; a refusal carries the contract's own words. `maxBatch` is how many reads one query holds (32 by default), each game checked taking 4: its status, the Portal's blacklist, and the registry's two answers. A game's status is read on its own hub, which the factory names: a game type's hub can change, and the games made before stay where they were.
257
- - What a deployment doesn't change is read once: each game's hub, each game type's hub, each hub's clock limit. `cache`, a `Map` of text by default, is where they're kept: an app can keep them across sessions.
261
+ - What a deployment doesn't change is read once: each game's hub, each game type's hub, each hub's clock limit, and a withdrawal's receipt once its DuskEVM block is final. A receipt is read after DuskEVM's finalized block, one read for the receipts read at the same time, and kept when its block is at or under that one; one that isn't final yet, or isn't a withdrawal's, is read again each time. `cache`, a `Map` of text by default, is where they're kept: an app can keep them across sessions.
258
262
  - Dusk's latest block comes from `latestBlock`, or the node's GraphQL. Without it, a proved withdrawal's status fails: its date can't be worked out, and no claim is built.
259
263
 
260
264
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dusk-network/app-core",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
4
4
  "description": "Shared logic for the Dusk apps: public and shielded history, activity rows, bridge arrivals and DuskEVM withdrawals.",
5
5
  "license": "MPL-2.0",
6
6
  "type": "module",
package/src/activity.d.ts CHANGED
@@ -248,7 +248,11 @@ export type ActivityRow = {
248
248
  route: Route | null;
249
249
  /** A call of Sozu's pool (`isSozuCall`): whether it staked or unstaked. */
250
250
  sozu: "stake" | "unstake" | null;
251
- /** A call to DuskEVM's bridge or Portal. */
251
+ /**
252
+ * A call to DuskEVM's bridge or Portal. What came in from an account has
253
+ * none, whatever its transaction called: a gas refund from another
254
+ * account's call to the bridge is a transfer from that account.
255
+ */
252
256
  bridge: ActivityBridge | null;
253
257
  /**
254
258
  * A call of the name service, as the app's hook reads it; a reservation
package/src/activity.js CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { networkFor, routeForAccount } from "@dusk-network/networks";
11
+ import { isAccount } from "@dusk-network/networks/account";
11
12
  import { BRIDGE_FUNCTIONS, isBridgeContract } from "@dusk-network/networks/duskevm";
12
13
  import { SOZU_FN, isSozuCall } from "@dusk-network/networks/sozu";
13
14
 
@@ -570,7 +571,12 @@ export function activityRows({
570
571
  : null;
571
572
  const sozuCall = Boolean(c?.sozu) || Boolean(net && isSozuCall(net, { contractId, fnName }, { pools: poolList }));
572
573
  const call = c?.bridge ?? s?.bridge ?? null;
573
- const touchesBridge = Boolean(call) || Boolean(net && [contractId, counterparty].some((id) => isBridgeContract(/** @type {Network} */ (net), id)));
574
+ const touchesBridge =
575
+ (Boolean(call) || Boolean(net && [contractId, counterparty].some((id) => isBridgeContract(/** @type {Network} */ (net), id)))) &&
576
+ // What came in from an account isn't the bridge's, whatever its
577
+ // transaction called: a gas refund from another account's call to the
578
+ // bridge is a transfer from that account.
579
+ !(direction === "in" && isAccount(counterparty));
574
580
  const bridge = touchesBridge ? bridgeOf({ call, direction, fnName, value }) : null;
575
581
 
576
582
  /** @type {ActivityRow["nameCall"]} */
package/src/bridge.d.ts CHANGED
@@ -73,7 +73,11 @@ export declare const WAIT_MS: 604800000;
73
73
  /** Pages of an address's incoming calls on DuskEVM's explorer read at most, by default. */
74
74
  export declare const EXPLORER_PAGES: 5;
75
75
 
76
- /** Blocks one log search asks for at once; narrower where the RPC asks for it. */
76
+ /**
77
+ * Blocks a search's first log search asks for at once: each one after asks
78
+ * for twice as many as one the RPC answered whole, up to 100,000, and fewer
79
+ * where the RPC asks for it. The width is kept in the search's `scan`.
80
+ */
77
81
  export declare const LOG_WINDOW: 5000;
78
82
 
79
83
  /** Log searches one call makes for a group of equal transfers, by default: one more where the search ahead takes them all while its blocks are read again. */
@@ -117,8 +121,10 @@ export type HistoryScan = {
117
121
  * before (`recheck`): where the one under way is (`from`, null between
118
122
  * two), when the next one begins (`at`), how many are done, as each credit
119
123
  * seen has the passes done before it was found (`pass`), when the search
120
- * first reached the head (`since`), in milliseconds, and the widest window
121
- * the blocks are read again in, where the RPC refused a wider one (`size`).
124
+ * first reached the head (`since`), in milliseconds (and the width of the
125
+ * windows they were read in, as an earlier version kept it: `size`). The
126
+ * width of the next search's windows (`size`), and the narrowest the RPC
127
+ * refused (`refused`): it stays narrower than that.
122
128
  * With the archive, how far the transfer's own history is read
123
129
  * (`history`): a history longer than one call reads is read on from there,
124
130
  * before any search. Kept by the app with the credits it was made with,
@@ -131,6 +137,8 @@ export type Scan = {
131
137
  seen?: ReadonlyArray<{ hash: string; block: string; at: number; sender: string | null; pass?: number }>;
132
138
  start?: string;
133
139
  recheck?: { from: string | null; at: number; passes: number; since: number; size?: number };
140
+ size?: number;
141
+ refused?: number;
134
142
  history?: HistoryScan;
135
143
  };
136
144
 
@@ -294,8 +302,12 @@ export type ServiceArrivalOptions = {
294
302
  rpcUrl?: string;
295
303
  /** Transactions on the other chain that paid the app's other transfers, which aren't among `transfers`. */
296
304
  taken?: Iterable<string>;
297
- /** That chain's block times the app keeps. */
298
- blockTimes?: BlockTimes;
305
+ /**
306
+ * The block times the app keeps for each chain, by its chain ID: or one
307
+ * cache, for a network whose service routes are all on one chain (it
308
+ * isn't used for any other).
309
+ */
310
+ blockTimes?: BlockTimes | ((chainId: number) => BlockTimes | undefined);
299
311
  now?: number;
300
312
  waitMs?: number;
301
313
  maxWindows?: number;
@@ -397,6 +409,8 @@ export type HistoryLike = {
397
409
  /** Of `value`, a gas refund paid to another account than the sender. */
398
410
  refund?: bigint | string | null;
399
411
  memo: string;
412
+ /** True when `memo` is bytes that aren't text, given as 0x hex: no address or hash is read from it. */
413
+ memoBinary?: boolean;
400
414
  payer: boolean;
401
415
  timestamp?: number | null;
402
416
  success?: boolean | null;
@@ -421,7 +435,7 @@ export type ServiceTransferFacts = {
421
435
  /** What the route's account received: known only where no gas was left to refund; null otherwise. */
422
436
  amount: bigint | null;
423
437
  fee: bigint;
424
- /** The address its memo gives, exactly as the bridge reads it; null when it isn't one. */
438
+ /** The address its memo gives, exactly as the bridge reads it; null when it isn't one, or the memo isn't text. */
425
439
  to: string | null;
426
440
  at: number | null;
427
441
  height: number | null;
@@ -432,16 +446,18 @@ export type ServiceTransferFacts = {
432
446
  /**
433
447
  * A transfer the account sent to a service route's account, as its entry
434
448
  * says it; null for any other entry. The memo is read as the route's bridge
435
- * reads it: 0x and 40 hex digits, any case, nothing around them.
449
+ * reads it: 0x and 40 hex digits, any case, nothing around them, as text. A
450
+ * memo of bytes that aren't text (`memoBinary`) gives no address.
436
451
  */
437
452
  export declare function serviceTransferOf(entry: HistoryLike, network: NetworkRef): ServiceTransferFacts | null;
438
453
 
439
454
  /**
440
455
  * A payout of a migration, from its entry: a transfer the account received
441
456
  * from a migration route's payout account, with a transaction hash as the
442
- * memo, of its value less any gas `refund` paid to the account with it. Its
443
- * route is null where that account pays out more than one route. Null for
444
- * any other entry, and on a route without its payout account.
457
+ * memo as text, of its value less any gas `refund` paid to the account with
458
+ * it. Its route is null where that account pays out more than one route.
459
+ * Null for any other entry, a memo of bytes that aren't text included, and
460
+ * on a route without its payout account.
445
461
  */
446
462
  export declare function migrationPayoutOf(entry: HistoryLike, network: NetworkRef): MigrationPayout | null;
447
463