@dusk-network/app-core 0.1.0 → 0.3.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 +16 -0
- package/README.md +77 -2
- package/package.json +24 -2
- package/src/bridge.d.ts +416 -0
- package/src/bridge.js +1948 -0
- package/src/duskevm-withdrawals.d.ts +535 -0
- package/src/duskevm-withdrawals.js +2429 -0
- package/src/history.d.ts +9 -2
- package/src/history.js +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
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.3.0]
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `/duskevm-withdrawals`: a DuskEVM withdrawal's start, finding it again, its status and claim date, and its proof's and claim's calls, checked before they're handed over to sign
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `/bridge`: a migration is paid by its own route's payout account also where one account pays out both routes (a payout of one not listed then has a null `route`), and a payout read from an entry leaves out a gas refund paid with it, which `/history` entries now tell apart as `refund`
|
|
14
|
+
|
|
15
|
+
## [0.2.0]
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `/bridge`: `depositArrivals`, `migrationPayouts` and `serviceArrivals`, where a deposit to DuskEVM, a migration and a transfer out to BNB Smart Chain arrived, each credit paying one transfer, in order
|
|
20
|
+
|
|
5
21
|
## [0.1.0]
|
|
6
22
|
|
|
7
23
|
### Added
|
package/README.md
CHANGED
|
@@ -30,8 +30,10 @@ The entry points are the same on both, under each registry's name: `@dusk-networ
|
|
|
30
30
|
| `@dusk-network/app-core/history` | A public account's history from the Dusk archive, synced by block height into the app's store (`createHistory`), through the archive's GraphQL (`createArchive`, `historyArchiveUrl`); its entries as facts (`parseHistoryEntry`, `historyEntry`); a range of heights (`historyRange`); a transaction's details (`fetchTxDetails`, `fetchTxNonce`); and the heights a history covers (`addCoverage`, `topGap`, `shownDownTo`) |
|
|
31
31
|
| `@dusk-network/app-core/shielded` | A profile's shielded history from the notes its shielded sync keeps, each block asked for among kept decoys (`shieldedHistory`); its entries as facts (`shieldedEntries`); the blocks at the notes' heights (`fetchShieldedBlocks`); the note tree's heights and the decoys drawn from them (`noteTreeHeights`, `readNoteHeights`, `createLeafHeightTap`, `pickHeights`, `leafInfo`) |
|
|
32
32
|
| `@dusk-network/app-core/activity` | An account's activity as rows of facts, one per transaction, newest first, from its public and shielded history and the wallet's own sends (`activityRows`), and the sends the chain's rows now say all of (`settleSends`) |
|
|
33
|
+
| `@dusk-network/app-core/bridge` | The other side of the bridge's routes: where a deposit to DuskEVM arrived (`depositArrivals`), a migration's payout on Dusk (`migrationPayouts`), and where a transfer out to BNB Smart Chain arrived (`serviceArrivals`); the history's entries as those transfers (`depositOf`, `migrationPayoutOf`, `serviceTransferOf`); and the arrivals as `/activity` takes them (`arrivalsOf`) |
|
|
34
|
+
| `@dusk-network/app-core/duskevm-withdrawals` | A DuskEVM withdrawal's life, signing aside: its start on DuskEVM (`withdrawalCall`), finding an address's withdrawals (`syncWithdrawals`, `explorerWithdrawals`, `withdrawalsByNonce`), and on Dusk where each is, its proof and its claim, ready for the app's wallet to sign (`createWithdrawals`) |
|
|
33
35
|
|
|
34
|
-
Every entry point has its `.d.ts`. The one runtime dependency is `@dusk-network/networks`.
|
|
36
|
+
Every entry point has its `.d.ts`. The one runtime dependency is `@dusk-network/networks`. `/duskevm-withdrawals` also takes [viem](https://viem.sh) (`^2.55.2`), an optional peer: an app installs it to use that entry, and no other entry imports it.
|
|
35
37
|
|
|
36
38
|
## History
|
|
37
39
|
|
|
@@ -57,7 +59,7 @@ const { entries, complete } = await history.read({ limit: 50 });
|
|
|
57
59
|
- `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.
|
|
58
60
|
- `fillDetails(entries)` reads the details of entries shown before a run gave them any.
|
|
59
61
|
|
|
60
|
-
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`), `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 })`).
|
|
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 })`).
|
|
61
63
|
|
|
62
64
|
### The store
|
|
63
65
|
|
|
@@ -181,6 +183,79 @@ const { drop, keep } = settleSends({ public: entries, shielded, pending: sends }
|
|
|
181
183
|
|
|
182
184
|
By default a send adds the name typed for its recipient, or a shielded send's recipient or memo, which the shielded history doesn't have; `keeps(send)` says what else does. A failed send that tried to move an amount is always kept: the chain's row says it moved nothing.
|
|
183
185
|
|
|
186
|
+
## Bridge
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
import { arrivalsOf, depositArrivals, depositOf } from "@dusk-network/app-core/bridge";
|
|
190
|
+
|
|
191
|
+
const { arrivals } = await depositArrivals({
|
|
192
|
+
network: chainId,
|
|
193
|
+
deposits: entries.map((entry) => depositOf(entry, account)).filter(Boolean), // `/history`'s entries
|
|
194
|
+
taken: keptArrivals, // the DuskEVM transactions the app's other deposits arrived with
|
|
195
|
+
fetch,
|
|
196
|
+
});
|
|
197
|
+
// arrivals.get(hash): { state: "arrived", hash: "0x029e…", at: 1793080333000, amount: 500000000n, block: 3951755 }
|
|
198
|
+
arrivalsOf(arrivals); // Map { hash => { hash: "0x029e…", at: 1793080333000 } }: `/activity`'s `arrivals`
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- Each credit pays one transfer. Equal transfers (the same route, recipient and payout) are paid in the order they were sent: the oldest takes the first credit after it, from 2 minutes before its block, as the chains' clocks differ. The app passes the transfers it still waits for, and the credits its others arrived with (`taken`): none of those pays a waiting one, though an earlier transfer read from the chain takes its own back, as one given but not looked for does.
|
|
202
|
+
- 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
|
+
- 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
|
+
- 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 history too long to read, or 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, or a history cut short, 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
|
+
- A route without its payout account or address in the network's file pays out nothing that can be told apart: `no-route`.
|
|
208
|
+
- 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. 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.
|
|
209
|
+
|
|
210
|
+
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.
|
|
211
|
+
|
|
212
|
+
`activityRows` of `/activity` takes the deposits that arrived as its `arrivals`: `arrivalsOf` gives them by the deposit's hash, the DuskEVM transaction that paid it and when, and the deposit's row says where it arrived. Its `claims` are DuskEVM withdrawals', which this entry point doesn't read.
|
|
213
|
+
|
|
214
|
+
## DuskEVM withdrawals
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
import { createWithdrawals, withdrawalCall } from "@dusk-network/app-core/duskevm-withdrawals";
|
|
218
|
+
|
|
219
|
+
// The start, for the app's EVM wallet to send on DuskEVM.
|
|
220
|
+
const start = withdrawalCall(chainId, { amountWei, sender: evmAddress, to: duskAccount }); // { to, data, value }
|
|
221
|
+
|
|
222
|
+
const withdrawals = createWithdrawals({
|
|
223
|
+
network: chainId,
|
|
224
|
+
nodeUrl,
|
|
225
|
+
fetch: createMulticallFetch({ nodeUrl, multicallId }), // the reads one task starts go as one query
|
|
226
|
+
drivers: { optimismPortal: () => loadDriver(portalDriver), disputeGameFactory: () => loadDriver(factoryDriver) },
|
|
227
|
+
duskEvm: publicClient, // a viem client, or { url, fetch }
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const status = await withdrawals.status(txHash, { submitter: account, proofGame });
|
|
231
|
+
// { stage: "proven_waiting", reason: "game-in-progress", readyAt: 1793589639n, readyAtExact: false, facts: { amountWei, recipient, … }, … }
|
|
232
|
+
|
|
233
|
+
const { call, claim } = await withdrawals.prepareProof(txHash, { submitter: account });
|
|
234
|
+
// claim: when it can be claimed if proved now; call: { contractId, fnName, fnArgs, deposit: 0n, gasLimit, decoded }
|
|
235
|
+
const claimCall = await withdrawals.claimCall(txHash, { submitter: account }); // once `status` says "ready_to_finalize"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
- Only DUSK withdrawals on the bridge's route count, as Dusk's L1 bridge pays them out (`decodeWithdrawal` of `@dusk-network/networks/duskevm`): to Dusk's messenger, relaying DuskEVM's bridge to Dusk's, paying a Dusk account, or a contract only whole lux at the address DuskEVM gives it, and no one past a u64 of lux. Anything else is refused, for its status, its proof and its claim (`WithdrawalError`, `code: "not-a-withdrawal"`).
|
|
239
|
+
- `stage` is where the withdrawal is for the account whose proof counts (`submitter`, a Dusk account or its Portal address; its recipient's account when none is given): "waiting_for_output", "ready_to_prove", "proven_waiting", "ready_to_finalize" or "finalized". `reason` says why it waits. A claim date is worked out as the Portal checks a claim (`claimFrom`), counted from Dusk's latest block, and is exact once the proof's game has resolved; `readyAt` is absent when the game can't be read now, so the date known so far stands. A claim is ready once the Portal takes it, its game has resolved for the defender, and its date has come: never while the game, the Portal's blacklist or the anchor state registry's word on it can't be read, nor on the Portal's own check alone, which may take just the proof and its game. A proof whose game the Portal refuses (blacklisted, retired, no longer respected, or won by the challenger) can't be claimed, whatever that check says (`proofRejected`, "proof-rejected"). `provenByOther` says another account holds a proof that stands, which this one can't claim with.
|
|
240
|
+
- `proofGame`, the game the last status found the proof against, puts that game's reads in the same query as the Portal's: a check is one query.
|
|
241
|
+
- A proof goes against a game that covers the withdrawal and has already resolved for its proposal, where the anchor state registry calls it proper and respected and it came after the respected game type last changed, so its claim date is exact when it's made; otherwise against the newest game that covers it, among the newest 64 (`gameSearchDepth`), that the Portal takes a proof against: a game the Portal blacklisted, the registry calls improper or not respected, or the challenger won is passed over for an older one, as is one whose standing can't be read. Where none of those is one the Portal takes, a status offers a proof against one that has resolved, as `prepareProof` finds it, to an account that never proved and to one whose proof the Portal refuses. A proof isn't prepared for a claimed withdrawal, nor over a proof of the account's that stands: it would cost its fee for nothing. One the Portal refuses is made again against another game it takes.
|
|
242
|
+
- `prepareProof` and `claimCall` refuse what would do nothing or can't be made: "claimed", "proven", "no-proof", "not-ready" (with the status), and "gas" for a withdrawal whose own gas takes the limit over the app's bound (`gasLimitUpper`, 1,000,000,000 by default). The gas limit is 50M and the withdrawal's own gas, at least 1M; the price is the app's.
|
|
243
|
+
- A call is encoded by the Portal's data driver the app loaded, as the network's file names and pins it, and read back before it's handed over: its arguments must say exactly the withdrawal, the game, the output root proof and the proof's nodes they were built from, as Dusk Wallet reads them (`decoded`), or it's refused ("bad-call"). It sends no DUSK along; the app signs `fnArgs` as given, with no amount and no one paid but the account itself.
|
|
244
|
+
- `prepareProof` gives `at`, when it was prepared: an app that shows a proof's claim date sends that proof, for as long as it chooses.
|
|
245
|
+
- `claims(hashes, { submitter })` reads when the withdrawals an account proved can be claimed, by their hashes: claimed, ready, a date, none, or to prove again. Its map is `/activity`'s `claims`.
|
|
246
|
+
- `timing()` is how long a withdrawal takes: the Portal's two delays, and how often outputs come.
|
|
247
|
+
|
|
248
|
+
### Finding withdrawals
|
|
249
|
+
|
|
250
|
+
- `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.
|
|
251
|
+
- `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.
|
|
252
|
+
|
|
253
|
+
### Reads and caches
|
|
254
|
+
|
|
255
|
+
- 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.
|
|
256
|
+
- 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.
|
|
257
|
+
- 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.
|
|
258
|
+
|
|
184
259
|
## License
|
|
185
260
|
|
|
186
261
|
MPL-2.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dusk-network/app-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
"types": "./src/activity.d.ts",
|
|
22
22
|
"default": "./src/activity.js"
|
|
23
23
|
},
|
|
24
|
+
"./bridge": {
|
|
25
|
+
"types": "./src/bridge.d.ts",
|
|
26
|
+
"default": "./src/bridge.js"
|
|
27
|
+
},
|
|
28
|
+
"./duskevm-withdrawals": {
|
|
29
|
+
"types": "./src/duskevm-withdrawals.d.ts",
|
|
30
|
+
"default": "./src/duskevm-withdrawals.js"
|
|
31
|
+
},
|
|
24
32
|
"./package.json": "./package.json"
|
|
25
33
|
},
|
|
26
34
|
"files": [
|
|
@@ -35,9 +43,23 @@
|
|
|
35
43
|
"typecheck": "npx --yes --package=typescript@5.9.3 -- tsc -p test-types",
|
|
36
44
|
"check:versions": "node scripts/check-versions.js",
|
|
37
45
|
"check:jsr": "npx --yes jsr@0.14.3 publish --dry-run --allow-dirty",
|
|
38
|
-
"
|
|
46
|
+
"fetch:drivers": "node scripts/fetch-drivers.js",
|
|
47
|
+
"smoke:history": "node scripts/smoke-history.js",
|
|
48
|
+
"smoke:bridge": "node scripts/smoke-bridge.js",
|
|
49
|
+
"smoke:withdrawals": "node scripts/smoke-withdrawals.js"
|
|
39
50
|
},
|
|
40
51
|
"dependencies": {
|
|
41
52
|
"@dusk-network/networks": "0.3.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"viem": "^2.55.2"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"viem": {
|
|
59
|
+
"optional": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"viem": "2.55.2"
|
|
42
64
|
}
|
|
43
65
|
}
|
package/src/bridge.d.ts
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The other side of the bridge's routes, as facts, for the Dusk apps:
|
|
3
|
+
* whether a deposit to DuskEVM arrived there, whether a migration from BNB
|
|
4
|
+
* Smart Chain or Ethereum was paid out on Dusk, and whether a transfer out
|
|
5
|
+
* to another chain (Dusk to BNB Smart Chain) arrived there.
|
|
6
|
+
*
|
|
7
|
+
* - Each credit pays one transfer. Equal transfers (the same route,
|
|
8
|
+
* recipient and payout) are paid in the order they were sent: the oldest
|
|
9
|
+
* takes the first credit after it. The app passes the transfers it still
|
|
10
|
+
* waits for, and the credits its others arrived with (`taken`): none of
|
|
11
|
+
* those pays a waiting one, though an earlier transfer read from the
|
|
12
|
+
* chain takes its own back, as one given but not looked for does.
|
|
13
|
+
* - A credit counts from 2 minutes before its transfer's block: Dusk's
|
|
14
|
+
* clock and the other chain's can differ a little. A transfer's time is
|
|
15
|
+
* its Dusk block's, never the device's clock; one without it yet isn't
|
|
16
|
+
* looked for.
|
|
17
|
+
* - Where the credits don't say who sent the transfer (DuskEVM's explorer),
|
|
18
|
+
* or the transfers that could have made them aren't all known, a credit
|
|
19
|
+
* is a transfer's only when it can't be another's: within 2 minutes
|
|
20
|
+
* either side of its block, once those minutes are over, and alone there
|
|
21
|
+
* but for the credits of the other transfers given, a payment that
|
|
22
|
+
* failed counting too. Otherwise the transfer is unsure.
|
|
23
|
+
* - A migration is paid by a transfer to the account from its route's
|
|
24
|
+
* payout account, with the migration's transaction hash as the memo, of
|
|
25
|
+
* exactly its amount. A transfer out arrives with the DUSK token's
|
|
26
|
+
* `Transfer` from its route's payout address to the address its memo
|
|
27
|
+
* gives, of what the route's account received less the bridge's fee, as
|
|
28
|
+
* the transfer's own transaction says. A route without its payout account
|
|
29
|
+
* or address in the network's file pays out nothing that can be told
|
|
30
|
+
* apart. Facts that can't be read leave a transfer unsure, never paid.
|
|
31
|
+
* - Results are facts: arrived, with the other side's transaction, its
|
|
32
|
+
* time, its amount in lux and its block; waiting; or unknown, and why.
|
|
33
|
+
* The words are the app's.
|
|
34
|
+
* - The app owns all I/O and storage: it passes `fetch`, the URLs (the
|
|
35
|
+
* network's file's by default), `/history`'s archive for the Dusk side,
|
|
36
|
+
* and its own caches: the credits it gave other transfers, where the last
|
|
37
|
+
* search stopped, and the chain's block times. Nothing here keeps state
|
|
38
|
+
* outside the calls, so it runs in an extension's service worker as well
|
|
39
|
+
* as in a page.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```js
|
|
43
|
+
* import { arrivalsOf, depositArrivals, depositOf } from "@dusk/app-core/bridge";
|
|
44
|
+
*
|
|
45
|
+
* const { arrivals } = await depositArrivals({
|
|
46
|
+
* network: chainId,
|
|
47
|
+
* deposits: entries.map((entry) => depositOf(entry, account)).filter(Boolean),
|
|
48
|
+
* taken: keptArrivals.map((arrival) => arrival.hash), // the DuskEVM transactions other deposits arrived with
|
|
49
|
+
* fetch,
|
|
50
|
+
* });
|
|
51
|
+
* arrivals.get(hash); // { state: "arrived", hash: "0x029e…", at: 1793080333000, amount: 500000000n, block: 3951755 }
|
|
52
|
+
* arrivalsOf(arrivals); // `/activity`'s `arrivals`: the DuskEVM transaction that paid each deposit, and when
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @module
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import type { Account, EvmAddress, NetworkRef } from "@dusk-network/networks";
|
|
59
|
+
import type { Archive, Direction, EntryKind } from "./history.js";
|
|
60
|
+
|
|
61
|
+
/** How long before its transfer's block a credit still counts: Dusk's clock and the other chain's can differ. 2 minutes, in milliseconds. */
|
|
62
|
+
export declare const CLOCK_SLACK_MS: 120000;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* How long either side of its block a transfer's credit must come, where
|
|
66
|
+
* the credits can't otherwise be told apart: 2 minutes, in milliseconds.
|
|
67
|
+
*/
|
|
68
|
+
export declare const CREDIT_WINDOW_MS: 120000;
|
|
69
|
+
|
|
70
|
+
/** How long a transfer is waited for by default before it is unknown: 7 days, in milliseconds. */
|
|
71
|
+
export declare const WAIT_MS: 604800000;
|
|
72
|
+
|
|
73
|
+
/** Pages of an address's incoming calls on DuskEVM's explorer read at most, by default. */
|
|
74
|
+
export declare const EXPLORER_PAGES: 5;
|
|
75
|
+
|
|
76
|
+
/** Blocks one log search asks for at once; narrower where the RPC asks for it. */
|
|
77
|
+
export declare const LOG_WINDOW: 5000;
|
|
78
|
+
|
|
79
|
+
/** Log searches one call makes for a group of equal transfers, by default. */
|
|
80
|
+
export declare const LOG_WINDOWS: 8;
|
|
81
|
+
|
|
82
|
+
/** Equal deposits sent within this of each other are paid in their order: 2 hours, in milliseconds. */
|
|
83
|
+
export declare const DEPOSIT_HORIZON_MS: 7200000;
|
|
84
|
+
|
|
85
|
+
/** Equal transfers out sent within this of each other are paid in their order, or 4 times the route's usual time: 12 hours, in milliseconds. */
|
|
86
|
+
export declare const SERVICE_HORIZON_MS: 43200000;
|
|
87
|
+
|
|
88
|
+
/** Pages of a sender's history read for its earlier equal deposits. */
|
|
89
|
+
export declare const HISTORY_PAGES: 4;
|
|
90
|
+
|
|
91
|
+
/** `Transfer(address,address,uint256)`: the DUSK token's transfer that pays a transfer out. */
|
|
92
|
+
export declare const TRANSFER_TOPIC: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
93
|
+
|
|
94
|
+
/** `ETHBridgeFinalized(address,address,uint256,bytes)`: DuskEVM's bridge paying a deposit, from the depositor's DuskEVM address. */
|
|
95
|
+
export declare const ETH_BRIDGE_FINALIZED_TOPIC: "0x31b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83d";
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Where a search stopped: the block the next one starts at, the time of the
|
|
99
|
+
* transfer it began from, the logs it searched for (`search`), and the
|
|
100
|
+
* credits it found before (`seen`), which the next one counts again. A
|
|
101
|
+
* search for other logs, as for another sender or amount, starts afresh.
|
|
102
|
+
* Kept by the app with the credits it was made with, and passed back as it
|
|
103
|
+
* is.
|
|
104
|
+
*/
|
|
105
|
+
export type Scan = { from: string; basis: number; search: string; seen?: ReadonlyArray<{ hash: string; block: string; at: number; sender: string | null }> };
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Why a transfer's credit is unknown:
|
|
109
|
+
* - "unsure": a credit could be another transfer's;
|
|
110
|
+
* - "late": its window closed without a credit, and one coming later can't be told apart;
|
|
111
|
+
* - "beyond": the explorer's pages don't reach back to it;
|
|
112
|
+
* - "searching": the search used its windows before it read all it needs; it carries on from `scan`;
|
|
113
|
+
* - "expired": sent longer ago than it's waited for;
|
|
114
|
+
* - "no-route": the network has no such route, or the route's payout account or address isn't known;
|
|
115
|
+
* - "no-payout": a transfer out pays nothing out, as the chain shows it (`problem`);
|
|
116
|
+
* - "error": a read failed (`error`).
|
|
117
|
+
*/
|
|
118
|
+
export type UnknownReason = "unsure" | "late" | "beyond" | "searching" | "expired" | "no-route" | "no-payout" | "error";
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Why a transfer out pays nothing out, as the chain shows it: it "failed"
|
|
122
|
+
* on Dusk, went "elsewhere" than the route's account, paid it no more than
|
|
123
|
+
* the bridge's fee ("under-fee"), or has a memo that isn't an address as
|
|
124
|
+
* the route's bridge reads it ("no-address").
|
|
125
|
+
*/
|
|
126
|
+
export type NoPayout = "failed" | "elsewhere" | "under-fee" | "no-address";
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* What became of a transfer on the other side: "arrived", with the
|
|
130
|
+
* transaction there that paid it (an EVM transaction's hash, lower case, or
|
|
131
|
+
* a Dusk one's for a migration's payout), its block's time in milliseconds
|
|
132
|
+
* (null for a payout whose details aren't read yet), the lux it paid and its
|
|
133
|
+
* block; "waiting" for it; or "unknown", and why. `scan` is where the next
|
|
134
|
+
* search starts.
|
|
135
|
+
*/
|
|
136
|
+
export type Arrival =
|
|
137
|
+
| { state: "arrived"; hash: string; at: number | null; amount: bigint; block: number | null }
|
|
138
|
+
| { state: "waiting"; scan?: Scan }
|
|
139
|
+
| { state: "unknown"; reason: UnknownReason; error?: string; problem?: NoPayout; scan?: Scan };
|
|
140
|
+
|
|
141
|
+
/** A `fetch`, as these reads use it: a GET of the explorer's JSON, or a POST of JSON-RPC, and the answer's status and text. */
|
|
142
|
+
export type FetchLike = (
|
|
143
|
+
input: string,
|
|
144
|
+
init: { method: "GET" | "POST"; headers: Record<string, string>; body?: string; signal: AbortSignal }
|
|
145
|
+
) => Promise<{ ok: boolean; status: number; text(): Promise<string> }>;
|
|
146
|
+
|
|
147
|
+
/** The app's cache of a chain's block times, in milliseconds, by number, as a `Map` is. */
|
|
148
|
+
export type BlockTimes = {
|
|
149
|
+
get(block: number): number | undefined;
|
|
150
|
+
set(block: number, at: number): unknown;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** A deposit to DuskEVM that went through on Dusk (`depositOf`). Amounts in lux. */
|
|
154
|
+
export type Deposit = {
|
|
155
|
+
/** Its Dusk transaction's hash. */
|
|
156
|
+
hash: string;
|
|
157
|
+
amount: bigint | number | string;
|
|
158
|
+
/** The DuskEVM address it pays. */
|
|
159
|
+
to: string;
|
|
160
|
+
/** Its Dusk block's time, in milliseconds; null until the node gave it: it isn't looked for until then. */
|
|
161
|
+
at: number | null;
|
|
162
|
+
/** The Dusk account that sent it: on the RPC, its credit is found by that account's DuskEVM address. */
|
|
163
|
+
sender?: string | null;
|
|
164
|
+
/** Its Dusk block's height: where the sender's history is read back from. */
|
|
165
|
+
height?: number | null;
|
|
166
|
+
/** A DuskEVM block its credit can't be before: the head the app read before sending it. */
|
|
167
|
+
notBefore?: bigint | number | string | null;
|
|
168
|
+
/** Where the last search for it stopped, as its result gave it. */
|
|
169
|
+
scan?: Scan | null;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/** What `depositArrivals` takes. */
|
|
173
|
+
export type DepositArrivalOptions = {
|
|
174
|
+
/** The Dusk network, or its chain ID: its DuskEVM, and its URLs. */
|
|
175
|
+
network: NetworkRef;
|
|
176
|
+
/** The deposits waited for: each once, by hash. */
|
|
177
|
+
deposits: Iterable<Deposit>;
|
|
178
|
+
/**
|
|
179
|
+
* Where the credits are read: "explorer", the recipients' incoming calls
|
|
180
|
+
* on DuskEVM's explorer (its Blockscout API), the default; or "rpc",
|
|
181
|
+
* DuskEVM's bridge events on its RPC, by the sender's DuskEVM address.
|
|
182
|
+
*/
|
|
183
|
+
via?: "explorer" | "rpc";
|
|
184
|
+
/** The `fetch` to send requests with; `globalThis.fetch` when not given. */
|
|
185
|
+
fetch?: FetchLike;
|
|
186
|
+
/** DuskEVM's explorer API; the network's by default. */
|
|
187
|
+
explorerUrl?: string;
|
|
188
|
+
/** DuskEVM's RPC; the network's by default. */
|
|
189
|
+
rpcUrl?: string;
|
|
190
|
+
/**
|
|
191
|
+
* `/history`'s archive, for "rpc": each sender's equal deposits in the
|
|
192
|
+
* hours before its own are read from its history, those given but not
|
|
193
|
+
* looked for too, and paid first.
|
|
194
|
+
* Without it, or for a deposit without its sender and height, its credit
|
|
195
|
+
* is a deposit's only as where they can't be told apart.
|
|
196
|
+
*/
|
|
197
|
+
archive?: Archive;
|
|
198
|
+
/**
|
|
199
|
+
* DuskEVM transactions the app's other deposits arrived with: pass those
|
|
200
|
+
* deposits through these alone, not among `deposits`. No deposit looked
|
|
201
|
+
* for takes them; one read from a sender's history may, as its own.
|
|
202
|
+
*/
|
|
203
|
+
taken?: Iterable<string>;
|
|
204
|
+
/** DuskEVM's block times the app keeps, for "rpc". */
|
|
205
|
+
blockTimes?: BlockTimes;
|
|
206
|
+
/** The clock, in milliseconds. */
|
|
207
|
+
now?: number;
|
|
208
|
+
/** How long a deposit is waited for; `WAIT_MS` by default. */
|
|
209
|
+
waitMs?: number;
|
|
210
|
+
/** `CREDIT_WINDOW_MS` by default. */
|
|
211
|
+
windowMs?: number;
|
|
212
|
+
/** `EXPLORER_PAGES` by default. */
|
|
213
|
+
maxPages?: number;
|
|
214
|
+
/** `LOG_WINDOWS` by default. */
|
|
215
|
+
maxWindows?: number;
|
|
216
|
+
/** How long one request may take, in milliseconds: 8,000 by default. */
|
|
217
|
+
timeoutMs?: number;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Where each deposit arrived on DuskEVM, by its Dusk transaction's hash
|
|
222
|
+
* (lower case), in the order given, and the requests it took. Each credit
|
|
223
|
+
* pays one deposit, in the order they were sent.
|
|
224
|
+
*
|
|
225
|
+
* On the explorer, the recipient's incoming calls are read once for all its
|
|
226
|
+
* deposits, newest first, back past the oldest one waited for, at most
|
|
227
|
+
* `maxPages` pages; none is asked while every deposit's window is still
|
|
228
|
+
* open. On the RPC, a group of equal deposits from one sender is searched
|
|
229
|
+
* from about when the first was sent, `maxWindows` windows at most.
|
|
230
|
+
* A network without DuskEVM asks nothing. Throws a TypeError for a deposit
|
|
231
|
+
* that isn't one, and for a network there's no file for.
|
|
232
|
+
*/
|
|
233
|
+
export declare function depositArrivals(options: DepositArrivalOptions): Promise<{ arrivals: Map<string, Arrival>; requests: number }>;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* A transfer out to another chain on a service route, sent to the route's
|
|
237
|
+
* account on Dusk (`serviceTransferOf`). What it paid that account, the
|
|
238
|
+
* address its memo gives, and its block's height and time are read from
|
|
239
|
+
* the chain.
|
|
240
|
+
*/
|
|
241
|
+
export type ServiceTransfer = {
|
|
242
|
+
/** Its Dusk transaction's hash. */
|
|
243
|
+
hash: string;
|
|
244
|
+
/** Its route's ID, as "dusk-to-bsc". */
|
|
245
|
+
route: string;
|
|
246
|
+
/** The bridge's fee, in lux; the route's by default. */
|
|
247
|
+
fee?: bigint | number | string | null;
|
|
248
|
+
/** A block of the other chain its payout can't be before: the head the app read before sending it. */
|
|
249
|
+
notBefore?: bigint | number | string | null;
|
|
250
|
+
scan?: Scan | null;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/** What `serviceArrivals` takes. */
|
|
254
|
+
export type ServiceArrivalOptions = {
|
|
255
|
+
network: NetworkRef;
|
|
256
|
+
transfers: Iterable<ServiceTransfer>;
|
|
257
|
+
/** `/history`'s archive: each transfer, and the route's account's history, which lists the equal ones paid before. */
|
|
258
|
+
archive: Archive;
|
|
259
|
+
fetch?: FetchLike;
|
|
260
|
+
/** The other chain's RPC; the route's by default. */
|
|
261
|
+
rpcUrl?: string;
|
|
262
|
+
/** Transactions on the other chain that paid the app's other transfers, which aren't among `transfers`. */
|
|
263
|
+
taken?: Iterable<string>;
|
|
264
|
+
/** That chain's block times the app keeps. */
|
|
265
|
+
blockTimes?: BlockTimes;
|
|
266
|
+
now?: number;
|
|
267
|
+
waitMs?: number;
|
|
268
|
+
maxWindows?: number;
|
|
269
|
+
timeoutMs?: number;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Where each transfer out arrived on its chain, by its Dusk transaction's
|
|
274
|
+
* hash, in the order given, and the requests it took: the DUSK token's
|
|
275
|
+
* `Transfer` from the route's payout address to the address the memo gives,
|
|
276
|
+
* of what the route's account received less the fee. Each transfer is read
|
|
277
|
+
* from the chain, in one request: one the archive doesn't have yet is
|
|
278
|
+
* waiting, and one that pays nothing out is `no-payout`. The transfers the
|
|
279
|
+
* route's account received in the hours before that named the same address,
|
|
280
|
+
* in any case, and pay out the same are paid first: its history lists them,
|
|
281
|
+
* and their own transactions say what each received (one given but not
|
|
282
|
+
* looked for pays out what its own fee leaves). One that can't be
|
|
283
|
+
* read, or a history cut short, leaves the order unclear: unsure. Throws a
|
|
284
|
+
* TypeError without an archive, and for a network there's no file for.
|
|
285
|
+
*/
|
|
286
|
+
export declare function serviceArrivals(options: ServiceArrivalOptions): Promise<{ arrivals: Map<string, Arrival>; requests: number }>;
|
|
287
|
+
|
|
288
|
+
/** A migration into Dusk, as its chain shows it. */
|
|
289
|
+
export type Migration = {
|
|
290
|
+
/** Its transaction's hash on its chain. */
|
|
291
|
+
hash: string;
|
|
292
|
+
/** Its route's ID, as "bsc-to-dusk". */
|
|
293
|
+
route: string;
|
|
294
|
+
/** The DUSK it migrates, in lux. */
|
|
295
|
+
amount: bigint | number | string;
|
|
296
|
+
/** When it was sent, in milliseconds: it's waited for `waitMs` from then. */
|
|
297
|
+
at?: number | null;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/** A payout of a migration, as the account's history has it (`migrationPayoutOf`). */
|
|
301
|
+
export type MigrationPayout = {
|
|
302
|
+
/** The migration's transaction hash, from the memo, lower case. */
|
|
303
|
+
hash: `0x${string}`;
|
|
304
|
+
/**
|
|
305
|
+
* Its route's ID; null where its payer pays out more than one route's
|
|
306
|
+
* migrations, as one account set for both chains does: its transfer
|
|
307
|
+
* doesn't say which.
|
|
308
|
+
*/
|
|
309
|
+
route: string | null;
|
|
310
|
+
/** The Dusk account that paid it: its route's payout account. */
|
|
311
|
+
payer: string;
|
|
312
|
+
/** What it paid, in lux: a gas refund paid to the account with it aside. */
|
|
313
|
+
amount: bigint;
|
|
314
|
+
/** The payout's Dusk transaction. */
|
|
315
|
+
payout: string;
|
|
316
|
+
/** Its block's time, in milliseconds; null before its details are read. */
|
|
317
|
+
at: number | null;
|
|
318
|
+
/** Its block's height. */
|
|
319
|
+
block: number;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/** What `migrationPayouts` takes: the account's entries, or the archive and the account. */
|
|
323
|
+
export type MigrationPayoutOptions = {
|
|
324
|
+
network: NetworkRef;
|
|
325
|
+
migrations: Iterable<Migration>;
|
|
326
|
+
/** The account's public history, as `/history` reads or keeps it. */
|
|
327
|
+
entries?: Iterable<HistoryLike>;
|
|
328
|
+
/** Without `entries`: the archive's memo index is asked for each migration's payout, in one request. */
|
|
329
|
+
archive?: Archive;
|
|
330
|
+
/** The account the migrations pay, which the archive is read for. */
|
|
331
|
+
account?: string;
|
|
332
|
+
/** Payouts other migrations have. */
|
|
333
|
+
taken?: Iterable<string>;
|
|
334
|
+
now?: number;
|
|
335
|
+
waitMs?: number;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Each migration's payout on Dusk, by the migration's hash (lower case), in
|
|
340
|
+
* the order given; with `entries`, the payouts there of migrations not given
|
|
341
|
+
* (`unlisted`: sent from another device, or before); and the requests it
|
|
342
|
+
* took. Each payout pays one migration, of the route it was given with:
|
|
343
|
+
* from that route's payout account, even where that account pays out
|
|
344
|
+
* another route's too. A payout of the migration of another amount leaves
|
|
345
|
+
* it unsure, not waiting: the value of an entry an earlier version kept
|
|
346
|
+
* holds any gas refund paid to the account (`refund` tells it apart now);
|
|
347
|
+
* the memo index gives each payout's own.
|
|
348
|
+
*/
|
|
349
|
+
export declare function migrationPayouts(
|
|
350
|
+
options: MigrationPayoutOptions
|
|
351
|
+
): Promise<{ payouts: Map<string, Arrival>; unlisted: MigrationPayout[]; requests: number }>;
|
|
352
|
+
|
|
353
|
+
/** An entry of an account's public history, as `/history` reads or keeps it: amounts as bigints or decimal strings. */
|
|
354
|
+
export type HistoryLike = {
|
|
355
|
+
hash: string;
|
|
356
|
+
blockHeight: number;
|
|
357
|
+
direction: Direction;
|
|
358
|
+
kind: EntryKind;
|
|
359
|
+
counterparty: string | null;
|
|
360
|
+
value: bigint | string;
|
|
361
|
+
/** Of `value`, a gas refund paid to another account than the sender. */
|
|
362
|
+
refund?: bigint | string | null;
|
|
363
|
+
memo: string;
|
|
364
|
+
payer: boolean;
|
|
365
|
+
timestamp?: number | null;
|
|
366
|
+
success?: boolean | null;
|
|
367
|
+
bridge?: { kind: string; amount?: string; to?: string } | null;
|
|
368
|
+
[field: string]: unknown;
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* A deposit to DuskEVM the account sent, from its entry, once its details
|
|
373
|
+
* read its call: its amount and DuskEVM address, its block's time and
|
|
374
|
+
* height, and `account` as its sender. Null for any other entry, and for one
|
|
375
|
+
* that failed.
|
|
376
|
+
*/
|
|
377
|
+
export declare function depositOf(entry: HistoryLike, account?: Account): (Deposit & { amount: bigint; to: EvmAddress }) | null;
|
|
378
|
+
|
|
379
|
+
/** A transfer to a service route's account, and why it pays nothing out, if it doesn't. */
|
|
380
|
+
export type ServiceTransferFacts = {
|
|
381
|
+
hash: string;
|
|
382
|
+
route: string;
|
|
383
|
+
/** What left the account: the transfer, and any gas refund it paid another account, which the entry doesn't tell apart. */
|
|
384
|
+
sent: bigint;
|
|
385
|
+
/** What the route's account received: known only where no gas was left to refund; null otherwise. */
|
|
386
|
+
amount: bigint | null;
|
|
387
|
+
fee: bigint;
|
|
388
|
+
/** The address its memo gives, exactly as the bridge reads it; null when it isn't one. */
|
|
389
|
+
to: string | null;
|
|
390
|
+
at: number | null;
|
|
391
|
+
height: number | null;
|
|
392
|
+
/** "failed" on Dusk; "under-fee": no more than the fee left the account; "no-address": a memo that isn't an address; null when none is known. */
|
|
393
|
+
problem: "failed" | "under-fee" | "no-address" | null;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* A transfer the account sent to a service route's account, as its entry
|
|
398
|
+
* says it; null for any other entry. The memo is read as the route's bridge
|
|
399
|
+
* reads it: 0x and 40 hex digits, any case, nothing around them.
|
|
400
|
+
*/
|
|
401
|
+
export declare function serviceTransferOf(entry: HistoryLike, network: NetworkRef): ServiceTransferFacts | null;
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* A payout of a migration, from its entry: a transfer the account received
|
|
405
|
+
* from a migration route's payout account, with a transaction hash as the
|
|
406
|
+
* memo, of its value less any gas `refund` paid to the account with it. Its
|
|
407
|
+
* route is null where that account pays out more than one route. Null for
|
|
408
|
+
* any other entry, and on a route without its payout account.
|
|
409
|
+
*/
|
|
410
|
+
export declare function migrationPayoutOf(entry: HistoryLike, network: NetworkRef): MigrationPayout | null;
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* The deposits that arrived, as `/activity`'s `arrivals` takes them: by the
|
|
414
|
+
* deposit's hash, the DuskEVM transaction that paid it and when.
|
|
415
|
+
*/
|
|
416
|
+
export declare function arrivalsOf(arrivals: ReadonlyMap<string, Arrival>): Map<string, { hash: string; at: number | null }>;
|