@parity/product-deploy 0.8.1 → 0.8.2-rc.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/README.md +2 -230
- package/assets/environments.json +6 -0
- package/bin/bulletin-deploy +15 -1
- package/dist/bug-report.js +4 -4
- package/dist/{chunk-IRT4A7OO.js → chunk-2GBSYVK2.js} +139 -31
- package/dist/{chunk-CNPB4VAM.js → chunk-5K3RI5C2.js} +71 -0
- package/dist/{chunk-O2BFXY3Y.js → chunk-EJWZGSHD.js} +1 -1
- package/dist/{chunk-MXMVZU2Q.js → chunk-H64ZLWW2.js} +2 -2
- package/dist/{chunk-WPJADKC7.js → chunk-HPPLVGGC.js} +73 -42
- package/dist/{chunk-L2SKSKB6.js → chunk-J3NIXHZZ.js} +108 -0
- package/dist/{chunk-RW3GWDGI.js → chunk-JQ5X3VMP.js} +15 -102
- package/dist/{chunk-V5N5EYNV.js → chunk-NJPBXF5Z.js} +6 -3
- package/dist/{chunk-INVA3XGG.js → chunk-RIAMPAL2.js} +88 -77
- package/dist/{chunk-R2CJ5I5R.js → chunk-TQC3S6NP.js} +3 -3
- package/dist/{chunk-KB3EII7F.js → chunk-WSBDIHFZ.js} +1 -1
- package/dist/chunk-probe.js +3 -3
- package/dist/deploy.d.ts +38 -1
- package/dist/deploy.js +16 -10
- package/dist/dotns.d.ts +33 -2
- package/dist/dotns.js +9 -5
- package/dist/environments.d.ts +17 -4
- package/dist/environments.js +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -11
- package/dist/manifest/publish.js +11 -11
- package/dist/manifest-fetch.d.ts +22 -1
- package/dist/manifest-fetch.js +7 -1
- package/dist/manifest-roundtrip.js +1 -1
- package/dist/memory-report.js +2 -2
- package/dist/merkle.js +10 -10
- package/dist/personhood/bootstrap.js +5 -5
- package/dist/personhood/people-client.js +5 -5
- package/dist/pool.d.ts +2 -12
- package/dist/pool.js +3 -11
- package/dist/run-state.js +1 -1
- package/dist/telemetry.js +2 -2
- package/dist/version-check.js +3 -3
- package/docs/bootstrap.md +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,233 +1,5 @@
|
|
|
1
1
|
# bulletin-deploy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Early alpha code, stay tuned.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Quick Start
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install -g bulletin-deploy
|
|
11
|
-
|
|
12
|
-
# Build your app first, then deploy it.
|
|
13
|
-
bulletin-deploy ./dist my-app00.dot
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
On success, the CLI prints the CID and the `.dot` domain that now serves your app.
|
|
17
|
-
|
|
18
|
-
## Installation
|
|
19
|
-
|
|
20
|
-
- **Node.js 22+**
|
|
21
|
-
- **IPFS Kubo** if you want the default merkleization path
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
# macOS
|
|
25
|
-
brew install ipfs
|
|
26
|
-
ipfs init
|
|
27
|
-
|
|
28
|
-
# Linux
|
|
29
|
-
wget https://dist.ipfs.tech/kubo/v0.33.0/kubo_v0.33.0_linux-amd64.tar.gz
|
|
30
|
-
tar -xvzf kubo_v0.33.0_linux-amd64.tar.gz
|
|
31
|
-
sudo bash kubo/install.sh
|
|
32
|
-
ipfs init
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
If you do not want a Kubo dependency, pass `--js-merkle`.
|
|
36
|
-
|
|
37
|
-
Stable installs:
|
|
38
|
-
|
|
39
|
-
- `npm install -g bulletin-deploy`
|
|
40
|
-
- `npm install -g bulletin-deploy@latest`
|
|
41
|
-
|
|
42
|
-
Release candidates:
|
|
43
|
-
|
|
44
|
-
- `npm install -g bulletin-deploy@rc`
|
|
45
|
-
- `npm install -g bulletin-deploy@<exact-version>`
|
|
46
|
-
|
|
47
|
-
## CLI Usage
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
bulletin-deploy <build-dir> <domain.dot>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Examples:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
# Basic deploy (defaults to --env paseo-next-v2)
|
|
57
|
-
bulletin-deploy ./dist my-app00.dot
|
|
58
|
-
|
|
59
|
-
# Pick a different environment
|
|
60
|
-
bulletin-deploy ./dist my-app00.dot --env paseo-review
|
|
61
|
-
|
|
62
|
-
# List supported environments
|
|
63
|
-
bulletin-deploy --list-environments
|
|
64
|
-
|
|
65
|
-
# Refresh the environments cache before deploying
|
|
66
|
-
bulletin-deploy ./dist my-app00.dot --refresh-environments --env paseo-next-v2
|
|
67
|
-
|
|
68
|
-
# Direct signer deploy
|
|
69
|
-
bulletin-deploy ./dist my-app00.dot --mnemonic "..."
|
|
70
|
-
|
|
71
|
-
# Custom Bulletin RPC override (asset-hub still comes from --env)
|
|
72
|
-
bulletin-deploy ./dist my-app00.dot --rpc wss://custom-bulletin.example.com
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Selecting an environment
|
|
76
|
-
|
|
77
|
-
`--env <id>` selects a target environment by id. The list of environments and their RPC endpoints is sourced dynamically from [`paritytech/bulletin-deploy/assets/environments.json`](./assets/environments.json), which mirrors [`paritytech/triangle-status/environments.json`](https://github.com/paritytech/triangle-status/blob/main/environments.json) (the latter is private; bulletin-deploy serves as the public mirror).
|
|
78
|
-
|
|
79
|
-
| Env id | Network | Bulletin available? |
|
|
80
|
-
|---|---|---|
|
|
81
|
-
| `paseo-next-v2` (default) | testnet | yes |
|
|
82
|
-
| `paseo-next` | testnet | yes |
|
|
83
|
-
| `paseo-review` | testnet | yes |
|
|
84
|
-
| `previewnet` | testnet | yes |
|
|
85
|
-
| `polkadot` | mainnet | not yet |
|
|
86
|
-
| `kusama` | mainnet | not yet |
|
|
87
|
-
|
|
88
|
-
A single env id drives both the bulletin RPC and the asset-hub RPC used internally for DotNS, so they cannot drift. When you pass `--rpc`, it overrides only the bulletin endpoint within the chosen env; the asset-hub endpoint still comes from `--env`.
|
|
89
|
-
|
|
90
|
-
The runtime uses a 24-hour cache at `${XDG_CACHE_HOME:-~/.cache}/bulletin-deploy/environments.json`; `--refresh-environments` busts it and re-fetches. The npm tarball ships a bundled snapshot that is used when the live URL is unreachable.
|
|
91
|
-
|
|
92
|
-
### Options
|
|
93
|
-
|
|
94
|
-
| Flag | What it does |
|
|
95
|
-
|---|---|
|
|
96
|
-
| `--env <id>` | Target environment. Default: `paseo-next-v2`. See `--list-environments` for valid ids. |
|
|
97
|
-
| `--list-environments` | Print the environments table and exit. |
|
|
98
|
-
| `--refresh-environments` | Bust the cache and re-fetch environments.json. Composes with `--env` (refresh-then-deploy) or runs solo. |
|
|
99
|
-
| `--rpc wss://...` | Override the Bulletin RPC endpoint within the chosen `--env`. Also readable from `BULLETIN_RPC`. |
|
|
100
|
-
| `--mnemonic "..."` | Use a specific mnemonic as the direct signer for Bulletin uploads and DotNS updates. Also readable from `MNEMONIC`. |
|
|
101
|
-
| `--derivation-path "..."` | Apply a Substrate derivation path to `--mnemonic`, for example `//deploy/3`. |
|
|
102
|
-
| `--pool-size N` | Change the number of derived pool accounts available for pool-mode Bulletin uploads. Default: `10`. |
|
|
103
|
-
| `--password "..."` | Encrypt SPA content before upload. Consumers must provide the password to decrypt it. |
|
|
104
|
-
| `--js-merkle` | Use pure-JS merkleization instead of the Kubo binary. |
|
|
105
|
-
| `--tag "..."` | Attach a free-form telemetry label. Also readable from `DEPLOY_TAG`. |
|
|
106
|
-
| `--gh-pages-mirror` | After a successful deploy, push the generated CAR to the current repo's `gh-pages` branch as an HTTP mirror. |
|
|
107
|
-
| `--input-car <path>` | Deploy from a pre-built CAR file instead of a build directory. Skips merkleization; reads the root CID from the CAR header. Usage: `bulletin-deploy --input-car site.car my-app.dot` |
|
|
108
|
-
| `--version` | Print the CLI version. |
|
|
109
|
-
| `--help` | Show help. |
|
|
110
|
-
|
|
111
|
-
## Concepts
|
|
112
|
-
|
|
113
|
-
- `Bulletin`: the chain that stores the app payload in chunked transaction storage.
|
|
114
|
-
- `.dot domain`: the DotNS name that points at the deployed content.
|
|
115
|
-
- `CAR`: the content-addressed archive produced from your build output before upload.
|
|
116
|
-
- `merkleization`: turning a directory into a content-addressed DAG and CAR file.
|
|
117
|
-
- `pool accounts`: derived Bulletin uploader accounts used to spread nonce and authorization load.
|
|
118
|
-
- `PoP`: Proof of Personhood, which some `.dot` names require before registration.
|
|
119
|
-
|
|
120
|
-
## Incremental Upload
|
|
121
|
-
|
|
122
|
-
After the first deploy of a `.dot` domain, every subsequent deploy automatically reuses chunks already stored on Bulletin instead of re-uploading them. There is no flag to enable; it just runs.
|
|
123
|
-
|
|
124
|
-
How it works:
|
|
125
|
-
|
|
126
|
-
1. The previous deploy embeds a manifest at `.bulletin-deploy/manifest.json` inside the deployed content (file classification, block ordering, chunk metadata).
|
|
127
|
-
2. The new deploy fetches the previous contenthash from DotNS, then fetches that manifest via the Bulletin IPFS gateway.
|
|
128
|
-
3. The new build's CAR is sliced into chunks; each chunk's CID is HEAD-probed against the gateway. Chunks already present are skipped.
|
|
129
|
-
4. Only the chunks that actually changed (typically: the manifest itself and any modified content) are uploaded.
|
|
130
|
-
|
|
131
|
-
The summary line at the end of a deploy shows the savings:
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
Cache:
|
|
135
|
-
Manifest: embedded (1 attempt)
|
|
136
|
-
Probed: 18 chunks → 15 cached, 2 to upload, 1 probe-failed
|
|
137
|
-
Recycled: 3 CIDs found on-chain that weren't in the previous manifest
|
|
138
|
-
Saved: ~52s and 14.3 MB upload
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
CI runners benefit identically — no `actions/cache` wiring required, because the manifest travels with the deployed content rather than living on the runner's disk.
|
|
142
|
-
|
|
143
|
-
**Encrypted deploys** (`--password`) bypass the incremental path. Encryption produces non-deterministic CAR bytes per run, so chunk-level dedup can't apply.
|
|
144
|
-
|
|
145
|
-
**Force a full re-upload** by deleting `.bulletin-deploy/` from your build output (or changing one byte in any non-volatile file) before deploying. The classifier treats `.bulletin-deploy/` paths as volatile, so removing the manifest forces the next deploy to fall back to the heuristic classifier.
|
|
146
|
-
|
|
147
|
-
## Domain Rules
|
|
148
|
-
|
|
149
|
-
DotNS classifies labels on-chain and may require a specific Proof of Personhood level before registration.
|
|
150
|
-
|
|
151
|
-
Typical cases:
|
|
152
|
-
|
|
153
|
-
| Domain pattern | Typical classification |
|
|
154
|
-
|---|---|
|
|
155
|
-
| Base name, for example `my-app.dot` | `ProofOfPersonhoodFull` |
|
|
156
|
-
| Name with trailing digits, for example `my-app00.dot` | often `NoStatus`, but not guaranteed |
|
|
157
|
-
|
|
158
|
-
Do not rely on the string shape alone. The on-chain classifier is authoritative.
|
|
159
|
-
|
|
160
|
-
On testnets, `bulletin-deploy` self-grants PoP only when the classifier says it is needed. On mainnet, PoP cannot be self-granted.
|
|
161
|
-
|
|
162
|
-
## GitHub Pages Mirror
|
|
163
|
-
|
|
164
|
-
`--gh-pages-mirror` is an opt-in cache path for hosts that want an HTTP fetch path in addition to Bulletin.
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
bulletin-deploy ./dist my-app.dot --gh-pages-mirror
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
After a successful deploy, the CLI pushes:
|
|
171
|
-
|
|
172
|
-
- `bulletin/<domain>.dot.car`
|
|
173
|
-
- `bulletin/<domain>.dot.json`
|
|
174
|
-
|
|
175
|
-
to the current repo's `gh-pages` branch and prints the Pages URL.
|
|
176
|
-
|
|
177
|
-
Use it when you want to validate or consume the mirror feature. The source of truth remains Bulletin plus DotNS.
|
|
178
|
-
|
|
179
|
-
## Programmatic API
|
|
180
|
-
|
|
181
|
-
```js
|
|
182
|
-
import { deploy } from "bulletin-deploy";
|
|
183
|
-
|
|
184
|
-
const result = await deploy("./dist", "my-app00.dot");
|
|
185
|
-
console.log(result.cid, result.domainName);
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
For environments without Kubo:
|
|
189
|
-
|
|
190
|
-
```js
|
|
191
|
-
await deploy("./dist", "my-app00.dot", { jsMerkle: true });
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
## Environment Variables
|
|
195
|
-
|
|
196
|
-
| Variable | Default | Description |
|
|
197
|
-
|---|---|---|
|
|
198
|
-
| `BULLETIN_RPC` | `wss://paseo-bulletin-rpc.polkadot.io` | Override the Bulletin chain WebSocket RPC for the chosen `--env`. |
|
|
199
|
-
| `BULLETIN_ENVIRONMENTS_URL` | bulletin-deploy public mirror | Override the runtime URL for environments.json. Internal teams point this at a fork or local proxy. |
|
|
200
|
-
| `BULLETIN_DEPLOY_TELEMETRY` | off for external users, on for internal users | `1` to opt in, `0` to force off |
|
|
201
|
-
| `BULLETIN_DEPLOY_UPDATE_CHECK` | `1` | Set to `0` to disable version checks on failure |
|
|
202
|
-
| `IPFS_CID` | unset | Skip storage and reuse an existing CID |
|
|
203
|
-
| `DEPLOY_TAG` | unset | Telemetry label equivalent to `--tag` |
|
|
204
|
-
| `BULLETIN_DEPLOY_HOST_APP` | unset | Name of the host app embedding bulletin-deploy (e.g. `playground-cli`). Sets `deploy.host_app` on telemetry spans. |
|
|
205
|
-
| `BULLETIN_DEPLOY_HOST_APP_VERSION` | unset | Version of the host app. Sets `deploy.host_app_version` on telemetry spans when `BULLETIN_DEPLOY_HOST_APP` is also set. |
|
|
206
|
-
|
|
207
|
-
## Troubleshooting
|
|
208
|
-
|
|
209
|
-
| Error | What to check |
|
|
210
|
-
|---|---|
|
|
211
|
-
| `Requires Full Personhood verification` | The chosen label needs a higher PoP level. |
|
|
212
|
-
| `Domain ... is owned by a different account` | The `.dot` name is already owned by the account indicated. Use that account as parameter or transfer the domain from that account to the new account you want to use |
|
|
213
|
-
| `Account ... is not authorized for Bulletin storage` | The uploader account is not authorized on Bulletin yet. For operator-managed pools, see [`bulletin-bootstrap`](docs/bootstrap.md). |
|
|
214
|
-
| `fetchNonce timed out` or connection errors | The Bulletin RPC may be unhealthy. Try another endpoint. |
|
|
215
|
-
| `IPFS CLI not installed` | Install Kubo or switch to `--js-merkle`. |
|
|
216
|
-
| Previous deploy did not exit cleanly / OOM hint | Retry with a larger Node heap, for example `NODE_OPTIONS='--max-old-space-size=8192'`. |
|
|
217
|
-
|
|
218
|
-
## Contributing
|
|
219
|
-
|
|
220
|
-
New to the codebase? Start with **[ONBOARDING.md](ONBOARDING.md)** — it covers install, the mental model, the first task, and the working conventions for this repo (worktree-per-branch, squash-merge policy, never-delete-tests, where the per-directory rules live).
|
|
221
|
-
|
|
222
|
-
The team uses Claude Code as a primary tool. The repo ships team-shared Claude configuration: `.claude/skills/` (project-specific commands like `/e2e-local`, `/dotns-diagnose`, `/sentry-query`), `.claude/settings.json` (Bash allowlist), and per-directory `CLAUDE.md` files in `src/`, `sentry/`, `test/`, `tools/` that load on demand. Running `claude` inside this repo picks all of that up automatically.
|
|
223
|
-
|
|
224
|
-
Already have Claude Code installed? Clone, `npm install`, open `ONBOARDING.md`. New to Claude Code itself? The same doc covers install.
|
|
225
|
-
|
|
226
|
-
For maintainers and engineers familiar with the release flow, the canonical procedures live in the root [`CLAUDE.md`](CLAUDE.md): change workflow, dual-stage RC → stable release, post-release Sentry monitoring, and the squash-merge convention that satisfies branch protection without per-commit GPG signing.
|
|
227
|
-
|
|
228
|
-
## More Docs
|
|
229
|
-
|
|
230
|
-
- [Bootstrap and operator setup](docs/bootstrap.md)
|
|
231
|
-
- [Testing](docs/testing.md)
|
|
232
|
-
- [Telemetry](docs/telemetry.md)
|
|
233
|
-
- [E2E one-time setup](docs/e2e-bootstrap.md)
|
|
5
|
+
Full documentation and contributing guide: [docs-internal/README.dev.md](docs-internal/README.dev.md)
|
package/assets/environments.json
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"autoAccountMapping": true,
|
|
12
12
|
"nativeToEthRatio": 100000000,
|
|
13
13
|
"registerStorageDeposit": 2000000000000,
|
|
14
|
+
"popSelfServe": {
|
|
15
|
+
"sudoEnvLabel": "Preview",
|
|
16
|
+
"personhoodFaucetUrl": "https://sudo.personhood.dev/personhood-faucet",
|
|
17
|
+
"dotnsBootstrapUrl": "https://sudo.personhood.dev/dotns-bootstrap",
|
|
18
|
+
"stateAwareGuidance": true
|
|
19
|
+
},
|
|
14
20
|
"contracts": {
|
|
15
21
|
"DOTNS_PROTOCOL_REGISTRY": "0x984F17a9077808F4B7e127F76806A1D59546B5B6",
|
|
16
22
|
"DOTNS_REGISTRAR": "0x061273AeF34e8ab9Ca08E199d7440E2639Fc2088",
|
package/bin/bulletin-deploy
CHANGED
|
@@ -42,6 +42,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
42
42
|
}
|
|
43
43
|
(flags.contracts ??= {})[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
44
44
|
}
|
|
45
|
+
else if (args[i] === "--environment-file") { flags.environmentFile = args[++i]; }
|
|
45
46
|
else if (args[i] === "--list-environments") { flags.listEnvironments = true; }
|
|
46
47
|
else if (args[i] === "--password") { flags.password = args[++i]; }
|
|
47
48
|
else if (args[i] === "--js-merkle") { flags.jsMerkle = true; }
|
|
@@ -67,6 +68,12 @@ if (flags.publish && flags.unpublish) {
|
|
|
67
68
|
process.exit(1);
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
// --environment-file: propagate to the env var so all internal loadEnvironments()
|
|
72
|
+
// call sites (deploy.ts, manifest/publish.ts, personhood) honor the override.
|
|
73
|
+
if (flags.environmentFile) {
|
|
74
|
+
process.env.BULLETIN_DEPLOY_ENV_FILE = flags.environmentFile;
|
|
75
|
+
}
|
|
76
|
+
|
|
70
77
|
if (flags.version) {
|
|
71
78
|
console.log(`bulletin-deploy v${VERSION}`);
|
|
72
79
|
process.exit(0);
|
|
@@ -81,7 +88,7 @@ if (flags.removedBootstrap) {
|
|
|
81
88
|
// nothing else (deploy positional args are ignored).
|
|
82
89
|
if (flags.listEnvironments) {
|
|
83
90
|
try {
|
|
84
|
-
const { doc } = await loadEnvironments();
|
|
91
|
+
const { doc } = await loadEnvironments({ userFilePath: flags.environmentFile ?? process.env.BULLETIN_DEPLOY_ENV_FILE });
|
|
85
92
|
console.log(formatEnvironmentTable(listEnvironments(doc)));
|
|
86
93
|
process.exit(0);
|
|
87
94
|
} catch (e) {
|
|
@@ -169,6 +176,13 @@ Options:
|
|
|
169
176
|
--env <id> Target environment from environments.json (default: paseo-next-v2).
|
|
170
177
|
Drives both the bulletin RPC and the asset-hub RPC used
|
|
171
178
|
by DotNS. See --list-environments for valid ids.
|
|
179
|
+
--environment-file <path>
|
|
180
|
+
Path to a JSON file deep-merged over the bundled
|
|
181
|
+
environments.json. Override only the fields you need
|
|
182
|
+
(e.g. contract addresses after a chain reset); unspecified
|
|
183
|
+
fields fall through to the bundled values. Also honored
|
|
184
|
+
via BULLETIN_DEPLOY_ENV_FILE env var.
|
|
185
|
+
Warning: values are not validated against chain.
|
|
172
186
|
--list-environments Print the environments table and exit.
|
|
173
187
|
--contract <KEY>=<addr> Supply/override a DotNS contract address (repeatable).
|
|
174
188
|
Merged over the chosen --env's contracts map; required
|
package/dist/bug-report.js
CHANGED
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
offerBugReport,
|
|
10
10
|
scrubSecrets,
|
|
11
11
|
setDeployContext
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-H64ZLWW2.js";
|
|
13
|
+
import "./chunk-EJWZGSHD.js";
|
|
14
|
+
import "./chunk-RIAMPAL2.js";
|
|
15
|
+
import "./chunk-NJPBXF5Z.js";
|
|
16
16
|
export {
|
|
17
17
|
buildCliFlagsSummary,
|
|
18
18
|
buildLabels,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isTestnetSpecName
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JQ5X3VMP.js";
|
|
4
4
|
import {
|
|
5
5
|
captureWarning,
|
|
6
6
|
markCodePath,
|
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
setDeploySentryTag,
|
|
9
9
|
truncateAddress,
|
|
10
10
|
withSpan
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-RIAMPAL2.js";
|
|
12
12
|
import {
|
|
13
13
|
validateContractAddresses
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-5K3RI5C2.js";
|
|
15
15
|
import {
|
|
16
16
|
NonRetryableError
|
|
17
17
|
} from "./chunk-ZOC4GITL.js";
|
|
@@ -60,6 +60,10 @@ var FEE_FLOOR_REGISTER = ONE_PAS / 10n;
|
|
|
60
60
|
var TOP_UP_TARGET = ONE_PAS / 2n;
|
|
61
61
|
var SOURCE_BUFFER = ONE_PAS;
|
|
62
62
|
var MINIMUM_REGISTER_STORAGE_DEPOSIT = 2000000000000n;
|
|
63
|
+
var REGISTER_RENT_PRICE_WEI = 10n * 10n ** 18n;
|
|
64
|
+
function bufferedRentNative(nativeToEthRatio) {
|
|
65
|
+
return REGISTER_RENT_PRICE_WEI * 110n / (100n * nativeToEthRatio);
|
|
66
|
+
}
|
|
63
67
|
var REPROVE_FEE_ESTIMATE = ONE_PAS / 100n;
|
|
64
68
|
var REPROVE_FEE_SAFETY_MARGIN_PCT = 110n;
|
|
65
69
|
var TOP_UP_TRANSFER_TIMEOUT_MS = 6e4;
|
|
@@ -74,13 +78,13 @@ function resolveNativeTokenSymbol(envId) {
|
|
|
74
78
|
if (envId.includes("rococo")) return "ROC";
|
|
75
79
|
return "PAS";
|
|
76
80
|
}
|
|
77
|
-
function feeFloorFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT) {
|
|
81
|
+
function feeFloorFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT, rentPriceNative = 0n) {
|
|
78
82
|
if (plannedAction === "already-owned-by-us") return FEE_FLOOR_OWNED;
|
|
79
|
-
return FEE_FLOOR_REGISTER + storageDeposit;
|
|
83
|
+
return FEE_FLOOR_REGISTER + storageDeposit + rentPriceNative;
|
|
80
84
|
}
|
|
81
|
-
function topUpTargetFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT) {
|
|
85
|
+
function topUpTargetFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT, rentPriceNative = 0n) {
|
|
82
86
|
if (plannedAction === "already-owned-by-us") return TOP_UP_TARGET;
|
|
83
|
-
return TOP_UP_TARGET + storageDeposit;
|
|
87
|
+
return TOP_UP_TARGET + storageDeposit + rentPriceNative;
|
|
84
88
|
}
|
|
85
89
|
var RPC_ENDPOINTS = [
|
|
86
90
|
"wss://asset-hub-paseo.dotters.network",
|
|
@@ -120,6 +124,30 @@ function classifyTxRetryDecision(err) {
|
|
|
120
124
|
if (lower.includes("transaction watcher silent")) return "retry";
|
|
121
125
|
return "abort";
|
|
122
126
|
}
|
|
127
|
+
var DOTNS_RETRY_BASE_MS = 400;
|
|
128
|
+
var DOTNS_RETRY_MAX_MS = 6e3;
|
|
129
|
+
function dotnsRetryBackoffMs(attempt, rand = Math.random) {
|
|
130
|
+
const ceil = Math.min(DOTNS_RETRY_BASE_MS * 2 ** (attempt - 1), DOTNS_RETRY_MAX_MS);
|
|
131
|
+
return Math.round(ceil * (0.5 + rand() * 0.5));
|
|
132
|
+
}
|
|
133
|
+
function makeRetryStatusFilter(sink) {
|
|
134
|
+
let buffered = false;
|
|
135
|
+
return {
|
|
136
|
+
callback: (status) => {
|
|
137
|
+
if (status === "failed") {
|
|
138
|
+
buffered = true;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
sink(status);
|
|
142
|
+
},
|
|
143
|
+
flush: () => {
|
|
144
|
+
if (buffered) sink("failed");
|
|
145
|
+
},
|
|
146
|
+
reset: () => {
|
|
147
|
+
buffered = false;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
123
151
|
var DEFAULT_MNEMONIC = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
|
124
152
|
var _rpcIdCounter = 0;
|
|
125
153
|
async function fetchNonceFromEndpoint(rpc, ss58Address) {
|
|
@@ -533,7 +561,13 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
533
561
|
async getEvmAddress(substrateAddress) {
|
|
534
562
|
if (isAddress(substrateAddress)) return substrateAddress;
|
|
535
563
|
const address = await this.client.apis.ReviveApi.address(substrateAddress);
|
|
536
|
-
|
|
564
|
+
const hex = convertToHexString(address);
|
|
565
|
+
if (!hex || hex === "0x") {
|
|
566
|
+
throw new Error(
|
|
567
|
+
"ReviveApi.address returned empty result \u2014 RPC node may not support pallet-revive; try a different endpoint via DOTNS_RPC"
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
return hex;
|
|
537
571
|
}
|
|
538
572
|
async performDryRunCall(originSubstrateAddress, contractAddress, value, encodedData) {
|
|
539
573
|
if (isAddress(originSubstrateAddress)) throw new Error("performDryRunCall requires SS58 Substrate address, not EVM H160 address");
|
|
@@ -554,6 +588,21 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
554
588
|
if (!result.result.isOk) return { success: false, gasConsumed: result.gasConsumed, storageDeposit: result.storageDeposit.value, gasRequired: result.gasRequired, revertData: result.result.value.data, revertFlags: result.result.value.flags };
|
|
555
589
|
return { success: true, gasConsumed: result.gasConsumed, storageDeposit: result.storageDeposit.value, gasRequired: result.gasRequired };
|
|
556
590
|
}
|
|
591
|
+
// Returns true if the address holds contract code, false if it provably does
|
|
592
|
+
// not, and null if this runtime doesn't expose the storage map (caller must
|
|
593
|
+
// not treat null as "no code"). Used to disambiguate an empty-success (`0x`)
|
|
594
|
+
// contract read: on pallet-revive, calling an address with no code succeeds
|
|
595
|
+
// with empty return data, so empty `0x` from a read almost always means the
|
|
596
|
+
// configured contract address is wrong/undeployed rather than a real "unset".
|
|
597
|
+
async hasContractCode(address) {
|
|
598
|
+
try {
|
|
599
|
+
const info = await this.client.query.Revive.AccountInfoOf.getValue(address);
|
|
600
|
+
if (info === void 0 || info === null) return false;
|
|
601
|
+
return info.account_type?.type === "Contract";
|
|
602
|
+
} catch {
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
557
606
|
async checkIfAccountMapped(substrateAddress) {
|
|
558
607
|
try {
|
|
559
608
|
const evmAddress = await this.getEvmAddress(substrateAddress);
|
|
@@ -699,18 +748,23 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
699
748
|
});
|
|
700
749
|
}
|
|
701
750
|
async signAndSubmitWithRetry(buildExtrinsic, signer, statusCallback, label, opts = {}) {
|
|
751
|
+
const filter = makeRetryStatusFilter(statusCallback);
|
|
702
752
|
let lastError;
|
|
703
753
|
for (let attempt = 1; attempt <= DOTNS_TX_MAX_ATTEMPTS; attempt++) {
|
|
754
|
+
filter.reset();
|
|
704
755
|
try {
|
|
705
|
-
return await this.signAndSubmitExtrinsic(buildExtrinsic(), signer,
|
|
756
|
+
return await this.signAndSubmitExtrinsic(buildExtrinsic(), signer, filter.callback, opts);
|
|
706
757
|
} catch (e) {
|
|
707
758
|
lastError = e;
|
|
708
759
|
const decision = classifyTxRetryDecision(e);
|
|
709
760
|
if (decision === "abort" || attempt === DOTNS_TX_MAX_ATTEMPTS) break;
|
|
761
|
+
const ms = dotnsRetryBackoffMs(attempt);
|
|
710
762
|
const short = (e?.message ?? String(e)).slice(0, 80);
|
|
711
|
-
console.log(` ${label}: attempt ${attempt}/${DOTNS_TX_MAX_ATTEMPTS} failed (${short}), retrying
|
|
763
|
+
console.log(` ${label}: attempt ${attempt}/${DOTNS_TX_MAX_ATTEMPTS} failed (${short}), retrying in ${ms}ms\u2026`);
|
|
764
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
712
765
|
}
|
|
713
766
|
}
|
|
767
|
+
filter.flush();
|
|
714
768
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
715
769
|
}
|
|
716
770
|
// Dry-runs one Revive.call and returns the chain-side limits the live
|
|
@@ -841,6 +895,15 @@ function classifyAliasAccountRow(row) {
|
|
|
841
895
|
}
|
|
842
896
|
return { state: "wrong-context", storedContextHex: contextHex, revision };
|
|
843
897
|
}
|
|
898
|
+
function formatSelfServeSteps(popSelfServe) {
|
|
899
|
+
const steps = [];
|
|
900
|
+
if (popSelfServe.faucetUrl) {
|
|
901
|
+
steps.push(`Fund the service account mnemonic via ${popSelfServe.faucetUrl}`);
|
|
902
|
+
}
|
|
903
|
+
steps.push(`Go to ${popSelfServe.personhoodFaucetUrl}, pick your env (e.g. ${popSelfServe.sudoEnvLabel}), and paste the mnemonic`);
|
|
904
|
+
steps.push(`Go to ${popSelfServe.dotnsBootstrapUrl} and follow each step (first and last can probably be skipped)`);
|
|
905
|
+
return steps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
|
|
906
|
+
}
|
|
844
907
|
function formatPersonhoodRemediation(state, popSelfServe, environmentId) {
|
|
845
908
|
if (!popSelfServe?.stateAwareGuidance) {
|
|
846
909
|
return "Self-attestation is no longer available. Contact the DotNS team for whitelisting / Personhood status help.";
|
|
@@ -848,9 +911,7 @@ function formatPersonhoodRemediation(state, popSelfServe, environmentId) {
|
|
|
848
911
|
switch (state.state) {
|
|
849
912
|
case "not-bound":
|
|
850
913
|
return `Your account has no DotNS alias binding on ${environmentId ?? "this environment"}. On testnets you can self-serve:
|
|
851
|
-
|
|
852
|
-
2. Go to ${popSelfServe.personhoodFaucetUrl}, pick your env (e.g. ${popSelfServe.sudoEnvLabel}), and paste the mnemonic
|
|
853
|
-
3. Go to ${popSelfServe.dotnsBootstrapUrl} and follow each step (first and last can probably be skipped)`;
|
|
914
|
+
` + formatSelfServeSteps(popSelfServe);
|
|
854
915
|
case "bound-likely-stale":
|
|
855
916
|
return `Your alias binding exists but may have a stale ring revision. Run \`node tools/reprove-alias.mjs --mnemonic <your-mnemonic> --env ${environmentId ?? "this environment"}\` to refresh the proof, then retry the registration.`;
|
|
856
917
|
case "wrong-context":
|
|
@@ -868,12 +929,7 @@ function formatPopShortfallReason(opts) {
|
|
|
868
929
|
const state = aliasState ?? { state: "not-bound" };
|
|
869
930
|
testnetBlock = "\n\n" + formatPersonhoodRemediation(state, popSelfServe, environmentId);
|
|
870
931
|
} else {
|
|
871
|
-
testnetBlock =
|
|
872
|
-
|
|
873
|
-
On testnets you can self-serve:
|
|
874
|
-
1. Fund the service account mnemonic via ${popSelfServe.faucetUrl}
|
|
875
|
-
2. Go to ${popSelfServe.personhoodFaucetUrl}, pick your env (e.g. ${popSelfServe.sudoEnvLabel}), and paste the mnemonic
|
|
876
|
-
3. Go to ${popSelfServe.dotnsBootstrapUrl} and follow each step (first and last can probably be skipped)`;
|
|
932
|
+
testnetBlock = "\n\nOn testnets you can self-serve:\n" + formatSelfServeSteps(popSelfServe);
|
|
877
933
|
}
|
|
878
934
|
}
|
|
879
935
|
const alternativesBlock = `
|
|
@@ -987,12 +1043,16 @@ var DotNS = class {
|
|
|
987
1043
|
);
|
|
988
1044
|
console.log(` H160 Address: ${this.evmAddress}`);
|
|
989
1045
|
} catch (e) {
|
|
990
|
-
|
|
1046
|
+
const inner = e.message?.slice(0, 200) ?? String(e).slice(0, 200);
|
|
1047
|
+
const rpcHint = inner.includes("timed out") ? `; RPC: ${rpc} \u2014 retry or set DOTNS_RPC to another endpoint` : "";
|
|
1048
|
+
throw new Error(
|
|
1049
|
+
`DotNS connect: failed to resolve EVM address from ${this.substrateAddress} via ReviveApi.address (${inner})${rpcHint}`
|
|
1050
|
+
);
|
|
991
1051
|
}
|
|
992
1052
|
setDeployAttribute("deploy.dotns.rpc_used", rpc);
|
|
993
1053
|
setDeployAttribute("deploy.dotns.evm_address", this.evmAddress);
|
|
994
1054
|
this.connected = true;
|
|
995
|
-
|
|
1055
|
+
await this.resolveNativeToEthRatio(options);
|
|
996
1056
|
try {
|
|
997
1057
|
await this.ensureMappedAccountReady(options.autoAccountMapping ?? false);
|
|
998
1058
|
} catch (e) {
|
|
@@ -1092,6 +1152,35 @@ var DotNS = class {
|
|
|
1092
1152
|
ensureConnected() {
|
|
1093
1153
|
if (!this.connected) throw new Error("Not connected. Call connect() first.");
|
|
1094
1154
|
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Resolve the authoritative nativeToEthRatio for this session.
|
|
1157
|
+
*
|
|
1158
|
+
* Priority: chain constant (Revive.NativeToEthRatio) > options.nativeToEthRatio > default.
|
|
1159
|
+
* On mismatch between the env-configured value and the chain value, logs a WARNING naming
|
|
1160
|
+
* both values and proceeds with the chain value (it is the source of truth).
|
|
1161
|
+
* On query failure, falls back to the configured/default value without throwing.
|
|
1162
|
+
*
|
|
1163
|
+
* Must be called after clientWrapper is established (i.e. inside connect()).
|
|
1164
|
+
*/
|
|
1165
|
+
async resolveNativeToEthRatio(options) {
|
|
1166
|
+
const configuredRatio = options.nativeToEthRatio ?? NATIVE_TO_ETH_RATIO;
|
|
1167
|
+
this._nativeToEthRatio = configuredRatio;
|
|
1168
|
+
if (!this.clientWrapper) return;
|
|
1169
|
+
try {
|
|
1170
|
+
const chainValue = await this.clientWrapper.client.constants.Revive.NativeToEthRatio();
|
|
1171
|
+
const chainRatio = BigInt(chainValue);
|
|
1172
|
+
if (chainRatio !== configuredRatio) {
|
|
1173
|
+
const msg = `DotNS: Revive.NativeToEthRatio from chain (${chainRatio}) differs from configured value (${configuredRatio}); using chain value`;
|
|
1174
|
+
console.warn(msg);
|
|
1175
|
+
captureWarning("nativeToEthRatio mismatch: chain value overrides env config", {
|
|
1176
|
+
chain_value: String(chainRatio),
|
|
1177
|
+
configured_value: String(configuredRatio)
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
this._nativeToEthRatio = chainRatio;
|
|
1181
|
+
} catch {
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1095
1184
|
// Returns true when the DotNS chain (Asset Hub) reports a testnet spec_name.
|
|
1096
1185
|
// Used to gate test-only behaviors like self-granting Full PoP on a Lite
|
|
1097
1186
|
// signer for a NoStatus label.
|
|
@@ -1280,7 +1369,25 @@ var DotNS = class {
|
|
|
1280
1369
|
contracts: this._contracts
|
|
1281
1370
|
}));
|
|
1282
1371
|
}
|
|
1283
|
-
|
|
1372
|
+
const rawData = callResult.result.value.data ?? "0x";
|
|
1373
|
+
if (rawData.length <= 2) {
|
|
1374
|
+
const hasCode = await this.clientWrapper.hasContractCode(contractAddress);
|
|
1375
|
+
const name = dotnsContractName(contractAddress, this._contracts);
|
|
1376
|
+
if (hasCode === false) {
|
|
1377
|
+
throw new Error(
|
|
1378
|
+
`No contract deployed at ${contractAddress} (${name}) \u2014 the dry-run call to ${functionName} returned empty success data, which on pallet-revive means the target address has no contract code. Check environments.json / --contract config for this network.`
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
if (hasCode === null) {
|
|
1382
|
+
throw new Error(
|
|
1383
|
+
`Contract call returned empty data \u2014 contract=${name} (${contractAddress}) functionName=${functionName}. Could not verify whether contract code exists at this address (runtime code-presence query failed); investigate the contract/ABI or the configured address.`
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
throw new Error(
|
|
1387
|
+
`Contract call returned empty data \u2014 contract=${name} (${contractAddress}) functionName=${functionName}. The address has contract code but the call returned no bytes, which is unexpected for this read. Investigate the contract/ABI rather than masking it with a default.`
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
return decodeFunctionResult({ abi: contractAbi, functionName, data: rawData });
|
|
1284
1391
|
}
|
|
1285
1392
|
/**
|
|
1286
1393
|
* Like contractCall, but returns null when the chain replies with empty data
|
|
@@ -1314,7 +1421,7 @@ var DotNS = class {
|
|
|
1314
1421
|
}));
|
|
1315
1422
|
}
|
|
1316
1423
|
const rawData = callResult.result.value.data ?? "0x";
|
|
1317
|
-
if (rawData
|
|
1424
|
+
if (rawData.length <= 2) return null;
|
|
1318
1425
|
return decodeFunctionResult({ abi: contractAbi, functionName, data: rawData });
|
|
1319
1426
|
}
|
|
1320
1427
|
async contractTransaction(contractAddress, value, contractAbi, functionName, args = [], statusCallback = () => {
|
|
@@ -1422,7 +1529,7 @@ var DotNS = class {
|
|
|
1422
1529
|
if (txResolution.kind === TX_KIND_HASH) {
|
|
1423
1530
|
setDeployAttribute("deploy.subnode.tx", txResolution.hash);
|
|
1424
1531
|
if (txResolution.block) {
|
|
1425
|
-
setDeployAttribute("deploy.subnode.block", txResolution.block.number);
|
|
1532
|
+
setDeployAttribute("deploy.subnode.block", String(txResolution.block.number));
|
|
1426
1533
|
setDeployAttribute("deploy.subnode.block_hash", txResolution.block.hash);
|
|
1427
1534
|
console.log(` finalised @ block ${txResolution.block.number} (tx ${txResolution.hash})`);
|
|
1428
1535
|
} else {
|
|
@@ -1561,7 +1668,7 @@ var DotNS = class {
|
|
|
1561
1668
|
if (txRes.kind === TX_KIND_HASH) {
|
|
1562
1669
|
setDeployAttribute("deploy.contenthash.tx", txRes.hash);
|
|
1563
1670
|
if (txRes.block) {
|
|
1564
|
-
setDeployAttribute("deploy.contenthash.block", txRes.block.number);
|
|
1671
|
+
setDeployAttribute("deploy.contenthash.block", String(txRes.block.number));
|
|
1565
1672
|
setDeployAttribute("deploy.contenthash.block_hash", txRes.block.hash);
|
|
1566
1673
|
console.log(` finalised @ block ${txRes.block.number} (tx ${txRes.hash})`);
|
|
1567
1674
|
} else {
|
|
@@ -1979,9 +2086,7 @@ var DotNS = class {
|
|
|
1979
2086
|
const reservationInfo = await withTimeout(this.contractCall(this._contracts.POP_RULES, POP_RULES_ABI, "isBaseNameReserved", [baseName]), 3e4, "isBaseNameReserved");
|
|
1980
2087
|
const [isReserved, reservationOwner] = reservationInfo;
|
|
1981
2088
|
if (isReserved && reservationOwner.toLowerCase() !== this.evmAddress.toLowerCase()) throw new Error("Base name reserved for original Lite registrant");
|
|
1982
|
-
const
|
|
1983
|
-
const requiredStatus = typeof classificationResult[0] === "bigint" ? Number(classificationResult[0]) : classificationResult[0];
|
|
1984
|
-
const message = classificationResult[1];
|
|
2089
|
+
const { requiredStatus, message } = await this.classifyName(label);
|
|
1985
2090
|
const userStatus = await this.getUserPopStatus();
|
|
1986
2091
|
if (requiredStatus === ProofOfPersonhoodStatus.Reserved) throw new Error(message);
|
|
1987
2092
|
if (!canRegister(requiredStatus, userStatus)) {
|
|
@@ -2021,7 +2126,7 @@ var DotNS = class {
|
|
|
2021
2126
|
if (registerTxRes.kind === TX_KIND_HASH) {
|
|
2022
2127
|
setDeployAttribute("deploy.register.tx", registerTxRes.hash);
|
|
2023
2128
|
if (registerTxRes.block) {
|
|
2024
|
-
setDeployAttribute("deploy.register.block", registerTxRes.block.number);
|
|
2129
|
+
setDeployAttribute("deploy.register.block", String(registerTxRes.block.number));
|
|
2025
2130
|
setDeployAttribute("deploy.register.block_hash", registerTxRes.block.hash);
|
|
2026
2131
|
console.log(` finalised @ block ${registerTxRes.block.number} (tx ${registerTxRes.hash})`);
|
|
2027
2132
|
} else {
|
|
@@ -2271,13 +2376,14 @@ var DotNS = class {
|
|
|
2271
2376
|
// canProceed:true result with an actionable canProceed:false when even the
|
|
2272
2377
|
// top-up can't get the signer above the threshold.
|
|
2273
2378
|
async gateOnFeeBalance(candidate, signerFreeBalance, isTestnet) {
|
|
2274
|
-
const
|
|
2379
|
+
const rentPriceNative = candidate.plannedAction === "register" ? bufferedRentNative(this._nativeToEthRatio) : 0n;
|
|
2380
|
+
const feeFloor = feeFloorFor(candidate.plannedAction, this._registerStorageDeposit, rentPriceNative);
|
|
2275
2381
|
let effectiveBalance = signerFreeBalance;
|
|
2276
2382
|
let toppedUp;
|
|
2277
2383
|
if (effectiveBalance < feeFloor && isTestnet) {
|
|
2278
2384
|
setDeployAttribute("deploy.dotns.signer_below_floor", "true");
|
|
2279
2385
|
console.log(` DotNS signer ${this.substrateAddress?.slice(0, 8)}... balance ${fmtPas(effectiveBalance)} PAS < ${fmtPas(feeFloor)} PAS floor \u2014 attempting testnet auto top-up...`);
|
|
2280
|
-
const result = await this.attemptTestnetTopUp(this.substrateAddress, topUpTargetFor(candidate.plannedAction, this._registerStorageDeposit));
|
|
2386
|
+
const result = await this.attemptTestnetTopUp(this.substrateAddress, topUpTargetFor(candidate.plannedAction, this._registerStorageDeposit, rentPriceNative));
|
|
2281
2387
|
if (result) {
|
|
2282
2388
|
console.log(` Topped up ${fmtPas(result.transferred)} PAS from ${result.source}`);
|
|
2283
2389
|
effectiveBalance += result.transferred;
|
|
@@ -2462,6 +2568,8 @@ export {
|
|
|
2462
2568
|
WS_HEARTBEAT_TIMEOUT_MS,
|
|
2463
2569
|
DOTNS_TX_MAX_ATTEMPTS,
|
|
2464
2570
|
classifyTxRetryDecision,
|
|
2571
|
+
dotnsRetryBackoffMs,
|
|
2572
|
+
makeRetryStatusFilter,
|
|
2465
2573
|
DEFAULT_MNEMONIC,
|
|
2466
2574
|
fetchNonce,
|
|
2467
2575
|
verifyNonceAdvanced,
|