@defdone/annon 0.1.0-alpha.1 → 0.1.0-alpha.3
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 +26 -0
- package/CLI.md +173 -13
- package/README.md +47 -10
- package/dist/api/annon-types.d.ts +2 -1
- package/dist/api/annon.js +9 -2
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/login-bundle.js +61 -60
- package/dist/browser/login-dom.d.ts +13 -0
- package/dist/browser/login-dom.js +71 -0
- package/dist/browser/login-dropdown.d.ts +32 -0
- package/dist/browser/login-dropdown.js +129 -0
- package/dist/browser/login-format.d.ts +16 -0
- package/dist/browser/login-format.js +52 -0
- package/dist/browser/login-status.d.ts +70 -0
- package/dist/browser/login-status.js +85 -0
- package/dist/browser/login-steps.d.ts +6 -0
- package/dist/browser/login-steps.js +35 -0
- package/dist/browser/login-terms.d.ts +10 -0
- package/dist/browser/login-terms.js +69 -0
- package/dist/browser/login-view.d.ts +34 -0
- package/dist/browser/login-view.js +227 -0
- package/dist/browser/login-wallet.d.ts +28 -0
- package/dist/browser/login-wallet.js +179 -0
- package/dist/browser/login.js +154 -142
- package/dist/browser/purchase-bundle.js +29 -29
- package/dist/browser/purchases.d.ts +4 -0
- package/dist/browser/purchases.js +14 -0
- package/dist/browser/reconcile.d.ts +41 -0
- package/dist/browser/reconcile.js +77 -0
- package/dist/node/adapter-checks.d.ts +3 -1
- package/dist/node/adapter-checks.js +3 -1
- package/dist/node/adapter.js +10 -3
- package/dist/node/agent.js +2 -1
- package/dist/node/approval-status.d.ts +34 -0
- package/dist/node/approval-status.js +71 -0
- package/dist/node/backup-secret-init.d.ts +9 -0
- package/dist/node/backup-secret-init.js +103 -0
- package/dist/node/backup-secret.d.ts +35 -0
- package/dist/node/backup-secret.js +104 -0
- package/dist/node/browser-login.d.ts +2 -0
- package/dist/node/browser-login.js +95 -16
- package/dist/node/buy-commands.js +17 -9
- package/dist/node/buy-flow.d.ts +18 -0
- package/dist/node/buy-flow.js +89 -0
- package/dist/node/cli-flags.d.ts +10 -0
- package/dist/node/cli-flags.js +68 -0
- package/dist/node/cli.d.ts +9 -1
- package/dist/node/cli.js +87 -95
- package/dist/node/command-context.js +1 -1
- package/dist/node/config.d.ts +4 -0
- package/dist/node/config.js +6 -2
- package/dist/node/core-adapter.js +4 -0
- package/dist/node/login-page.d.ts +7 -0
- package/dist/node/login-page.js +77 -0
- package/dist/node/main.js +20 -2
- package/dist/node/output/discovery.d.ts +23 -0
- package/dist/node/output/discovery.js +52 -0
- package/dist/node/output/human.d.ts +10 -0
- package/dist/node/output/human.js +46 -0
- package/dist/node/output/purchase.d.ts +10 -0
- package/dist/node/output/purchase.js +78 -0
- package/dist/node/output/text.d.ts +10 -0
- package/dist/node/output/text.js +43 -0
- package/dist/node/purchase-commands.d.ts +2 -2
- package/dist/node/purchase-commands.js +14 -6
- package/dist/node/purchase-ref.d.ts +15 -0
- package/dist/node/purchase-ref.js +93 -0
- package/dist/node/saved-state.d.ts +4 -0
- package/dist/node/saved-state.js +50 -0
- package/dist/node/shell/complete.d.ts +2 -0
- package/dist/node/shell/complete.js +17 -0
- package/dist/node/shell/session.d.ts +20 -0
- package/dist/node/shell/session.js +219 -0
- package/dist/node/shell/welcome.d.ts +9 -0
- package/dist/node/shell/welcome.js +39 -0
- package/dist/node/shell/words.d.ts +1 -0
- package/dist/node/shell/words.js +36 -0
- package/dist/node/shops.d.ts +5 -0
- package/dist/node/shops.js +22 -0
- package/dist/node/version.d.ts +1 -0
- package/dist/node/version.js +3 -0
- package/dist/node/wallets.d.ts +1 -1
- package/dist/node/wallets.js +2 -10
- package/dist/pay/purchase.d.ts +2 -1
- package/dist/wallet/uncertain.d.ts +6 -3
- package/dist/wallet/uncertain.js +4 -1
- package/package.json +1 -1
package/dist/browser/login.js
CHANGED
|
@@ -1,37 +1,24 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { pageElements, renderSummary, showStatus } from './login-dom.js';
|
|
3
|
+
import { walletPicker } from './login-dropdown.js';
|
|
4
|
+
import { expiryText } from './login-format.js';
|
|
5
|
+
import { connected, SendUncertain, STATUS, walletFailure } from './login-status.js';
|
|
6
|
+
import { approvalSteps } from './login-steps.js';
|
|
7
|
+
import { describeRequest, technicalDetails } from './login-view.js';
|
|
8
|
+
import { pageWallets } from './login-wallet.js';
|
|
4
9
|
Object.assign(globalThis, { Buffer });
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const approve = document.getElementById('approve');
|
|
9
|
-
const nearChoice = document.getElementById('near-wallet');
|
|
10
|
-
const hotChoice = document.getElementById('near-wallet-hot');
|
|
11
|
-
/** Signing-request chains the page can bind a NEAR wallet to. Anything else is refused. */
|
|
12
|
-
const NEAR_NETWORKS = new Map([
|
|
13
|
-
['near:mainnet', 'mainnet'],
|
|
14
|
-
['near:testnet', 'testnet'],
|
|
15
|
-
]);
|
|
16
|
-
/** The selector's only RPC per network. The page CSP allows exactly these (browser-login.ts). */
|
|
17
|
-
const NEAR_RPC = {
|
|
18
|
-
mainnet: 'https://free.rpc.fastnear.com',
|
|
19
|
-
testnet: 'https://test.rpc.fastnear.com',
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* NEAR wallets offered on this page and the networks each is bound to. MyNearWallet is retired
|
|
23
|
-
* and no longer connects to apps. HOT's selector module sends no network with its requests, so
|
|
24
|
-
* it is offered for mainnet only and never loaded for another network.
|
|
25
|
-
*/
|
|
26
|
-
const NEAR_WALLETS = new Map([
|
|
27
|
-
['meteor-wallet', { name: 'Meteor Wallet', networks: ['mainnet', 'testnet'] }],
|
|
28
|
-
['hot-wallet', { name: 'HOT Wallet', networks: ['mainnet'] }],
|
|
29
|
-
]);
|
|
10
|
+
const el = pageElements(document);
|
|
11
|
+
const approve = el.approve;
|
|
12
|
+
const cancel = document.getElementById('cancel');
|
|
30
13
|
const fragment = new URLSearchParams(location.hash.slice(1));
|
|
31
14
|
const redirected = location.pathname === '/callback';
|
|
32
15
|
let capability = redirected ? '' : location.hash.slice(1);
|
|
33
16
|
history.replaceState(null, '', location.pathname);
|
|
34
|
-
|
|
17
|
+
let paused = false;
|
|
18
|
+
let finished = false;
|
|
19
|
+
let pollTimer;
|
|
20
|
+
const steps = approvalSteps(document, el);
|
|
21
|
+
const api = async (path, body, accepted) => {
|
|
35
22
|
const r = await fetch(path, {
|
|
36
23
|
method: 'POST',
|
|
37
24
|
headers: { authorization: `Bearer ${capability}`, 'content-type': 'application/json' },
|
|
@@ -42,11 +29,77 @@ const api = async (path, body) => {
|
|
|
42
29
|
});
|
|
43
30
|
if (!r.ok)
|
|
44
31
|
throw new Error('Session expired or wallet result rejected. Return to the terminal.');
|
|
32
|
+
accepted?.();
|
|
45
33
|
return r.json();
|
|
46
34
|
};
|
|
47
|
-
|
|
35
|
+
/** Requests whose result went to the CLI, or that ended here: never signed again. */
|
|
36
|
+
const done = new Set();
|
|
48
37
|
const callbacks = new Map();
|
|
38
|
+
const submitting = new Set();
|
|
39
|
+
const wallets = pageWallets({
|
|
40
|
+
expect: (state, id) => callbacks.set(state, id),
|
|
41
|
+
forget: (state) => callbacks.delete(state),
|
|
42
|
+
sending: async (id) => {
|
|
43
|
+
// The wallet may broadcast from now on: this request is never attempted again here, and the
|
|
44
|
+
// CLI records it before the wallet is called.
|
|
45
|
+
done.add(id);
|
|
46
|
+
await api('/api/report', { id, reason: 'send_started' });
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
49
|
let current;
|
|
50
|
+
let expiry;
|
|
51
|
+
let busy = false;
|
|
52
|
+
const status = (s) => showStatus(el, s);
|
|
53
|
+
const picker = walletPicker(document, () => controls());
|
|
54
|
+
const walletId = () => picker.value();
|
|
55
|
+
/** Buttons and wallet choice follow the request: usable only while it is open and idle. */
|
|
56
|
+
function controls() {
|
|
57
|
+
const q = current;
|
|
58
|
+
const open = !!q && !done.has(q.id) && Date.now() < q.expiresAt;
|
|
59
|
+
approve.disabled = !open || busy;
|
|
60
|
+
if (cancel) {
|
|
61
|
+
cancel.hidden = !open;
|
|
62
|
+
cancel.disabled = busy;
|
|
63
|
+
}
|
|
64
|
+
// No wallet switch while a wallet is open or once the request is finished or sent.
|
|
65
|
+
picker.setLocked(!open || busy);
|
|
66
|
+
approve.textContent =
|
|
67
|
+
q && wallets.needsConnect(q, walletId()) ? 'Connect wallet' : 'Review in wallet';
|
|
68
|
+
}
|
|
69
|
+
async function submit(id, result) {
|
|
70
|
+
if (submitting.has(id))
|
|
71
|
+
return;
|
|
72
|
+
submitting.add(id);
|
|
73
|
+
paused = true;
|
|
74
|
+
clearTimeout(pollTimer);
|
|
75
|
+
done.add(id);
|
|
76
|
+
status(STATUS.verifying);
|
|
77
|
+
controls();
|
|
78
|
+
try {
|
|
79
|
+
let accepted = false;
|
|
80
|
+
try {
|
|
81
|
+
const next = await api('/api/result', { id, result, follow: true }, () => {
|
|
82
|
+
accepted = true;
|
|
83
|
+
steps.finish();
|
|
84
|
+
finished = true;
|
|
85
|
+
status(STATUS.signed);
|
|
86
|
+
});
|
|
87
|
+
if (next.action && next.action.id !== id) {
|
|
88
|
+
show(next.action);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (!accepted)
|
|
93
|
+
throw error;
|
|
94
|
+
status(STATUS.signed);
|
|
95
|
+
}
|
|
96
|
+
paused = false;
|
|
97
|
+
pollTimer = setTimeout(() => void poll(), 1000);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
status(STATUS.rejected);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
50
103
|
if (redirected) {
|
|
51
104
|
// Only a per-action opaque nonce leaves this origin. The capability stays in the opener.
|
|
52
105
|
window.opener?.postMessage({
|
|
@@ -58,7 +111,7 @@ if (redirected) {
|
|
|
58
111
|
signature: fragment.get('signature'),
|
|
59
112
|
},
|
|
60
113
|
}, location.origin);
|
|
61
|
-
status.
|
|
114
|
+
status(STATUS.redirected);
|
|
62
115
|
}
|
|
63
116
|
else {
|
|
64
117
|
window.addEventListener('message', (event) => {
|
|
@@ -68,149 +121,108 @@ else {
|
|
|
68
121
|
if (!id)
|
|
69
122
|
return;
|
|
70
123
|
callbacks.delete(event.data.state);
|
|
71
|
-
void
|
|
72
|
-
.then(() => {
|
|
73
|
-
status.textContent = 'Proof verified. Return to your terminal.';
|
|
74
|
-
})
|
|
75
|
-
.catch((e) => {
|
|
76
|
-
status.textContent = e.message;
|
|
77
|
-
});
|
|
124
|
+
void submit(id, event.data.result);
|
|
78
125
|
});
|
|
79
126
|
}
|
|
127
|
+
/** Shows the CLI's current request; a finished one stays on screen with its outcome. */
|
|
128
|
+
function show(q, sendStarted = false) {
|
|
129
|
+
if (q && q.id !== current?.id) {
|
|
130
|
+
current = q;
|
|
131
|
+
finished = false;
|
|
132
|
+
steps.start();
|
|
133
|
+
expiry = renderSummary(document, el, describeRequest(q), technicalDetails(q));
|
|
134
|
+
el['wallet-field'].hidden = !q.operation.startsWith('near.');
|
|
135
|
+
picker.setDisabled('hot-wallet', q.chain !== 'near:mainnet');
|
|
136
|
+
status(wallets.needsConnect(q, walletId()) ? STATUS.connect : STATUS.ready);
|
|
137
|
+
}
|
|
138
|
+
const last = current;
|
|
139
|
+
// A send started from an earlier load of this page: the wallet may have broadcast it.
|
|
140
|
+
if (last && q && sendStarted && !done.has(last.id)) {
|
|
141
|
+
done.add(last.id);
|
|
142
|
+
status({ tone: 'error', text: walletFailure(new SendUncertain()).text });
|
|
143
|
+
}
|
|
144
|
+
if (!last || !expiry) {
|
|
145
|
+
status(STATUS.waitingCli);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
expiry.textContent = expiryText(last.expiresAt, Date.now());
|
|
149
|
+
const expired = Date.now() >= last.expiresAt;
|
|
150
|
+
// The CLI dropped the request (expired, cancelled or session over) before a result was sent.
|
|
151
|
+
if (!done.has(last.id) && (!q || expired)) {
|
|
152
|
+
done.add(last.id);
|
|
153
|
+
status(expired ? STATUS.expired : STATUS.ended);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
controls();
|
|
157
|
+
}
|
|
80
158
|
async function poll() {
|
|
159
|
+
if (paused)
|
|
160
|
+
return;
|
|
81
161
|
if (!capability) {
|
|
82
|
-
status.
|
|
162
|
+
status(STATUS.noCapability);
|
|
83
163
|
return;
|
|
84
164
|
}
|
|
85
165
|
try {
|
|
86
166
|
const r = await api('/api/action');
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
hotChoice.disabled =
|
|
93
|
-
current?.chain?.startsWith('near:') === true && current.chain !== 'near:mainnet';
|
|
94
|
-
setTimeout(() => {
|
|
167
|
+
if (paused)
|
|
168
|
+
return;
|
|
169
|
+
el.pair.textContent = String(r.pairingCode);
|
|
170
|
+
show(r.action ?? undefined, r.sendStarted === true);
|
|
171
|
+
pollTimer = setTimeout(() => {
|
|
95
172
|
void poll();
|
|
96
173
|
}, 1000);
|
|
97
174
|
}
|
|
98
175
|
catch (e) {
|
|
99
|
-
|
|
176
|
+
if (paused || finished)
|
|
177
|
+
return;
|
|
178
|
+
status({ tone: 'error', text: e.message });
|
|
100
179
|
capability = '';
|
|
101
180
|
approve.disabled = true;
|
|
102
181
|
}
|
|
103
182
|
}
|
|
104
183
|
approve.onclick = async () => {
|
|
105
184
|
const q = current;
|
|
106
|
-
if (!q ||
|
|
185
|
+
if (!q || busy || done.has(q.id) || Date.now() >= q.expiresAt)
|
|
107
186
|
return;
|
|
108
|
-
|
|
109
|
-
|
|
187
|
+
busy = true;
|
|
188
|
+
controls();
|
|
189
|
+
status(STATUS.wallet);
|
|
110
190
|
try {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const address = q.account || accounts[0];
|
|
118
|
-
if (!address)
|
|
119
|
-
throw new Error('No account');
|
|
120
|
-
const chain = await provider.request({ method: 'eth_chainId' });
|
|
121
|
-
if (`eip155:${BigInt(String(chain))}` !== q.chain)
|
|
122
|
-
throw new Error(`Select ${q.chain} in your wallet.`);
|
|
123
|
-
const wallet = evmWallet(provider, address.toLowerCase());
|
|
124
|
-
result =
|
|
125
|
-
q.operation === 'evm.personalSign'
|
|
126
|
-
? {
|
|
127
|
-
signature: (await wallet.authorize(q.payload.message))?.sig ?? '',
|
|
128
|
-
account: wallet.identity.id,
|
|
129
|
-
}
|
|
130
|
-
: { signature: await wallet.signTypedData(q.payload) };
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
const networkId = NEAR_NETWORKS.get(q.chain);
|
|
134
|
-
if (!networkId)
|
|
135
|
-
throw new Error(`Browser NEAR wallets support near:mainnet and near:testnet, not ${q.chain}.`);
|
|
136
|
-
const id = nearChoice?.value || 'meteor-wallet';
|
|
137
|
-
const offered = NEAR_WALLETS.get(id);
|
|
138
|
-
if (!offered)
|
|
139
|
-
throw new Error('Unknown NEAR wallet');
|
|
140
|
-
if (!offered.networks.includes(networkId))
|
|
141
|
-
throw new Error(`${offered.name} is offered for near:mainnet only. Pick Meteor Wallet.`);
|
|
142
|
-
// Only the chosen wallet is loaded, on first use: the page starts without wallet code
|
|
143
|
-
// touching storage or listeners, and a refused wallet never runs.
|
|
144
|
-
const [{ setupWalletSelector }, module] = await Promise.all([
|
|
145
|
-
import('@near-wallet-selector/core'),
|
|
146
|
-
id === 'hot-wallet'
|
|
147
|
-
? import('@near-wallet-selector/hot-wallet').then((m) => m.setupHotWallet())
|
|
148
|
-
: import('@near-wallet-selector/meteor-wallet').then((m) => m.setupMeteorWallet()),
|
|
149
|
-
]);
|
|
150
|
-
const selector = await setupWalletSelector({
|
|
151
|
-
network: networkId,
|
|
152
|
-
fallbackRpcUrls: [NEAR_RPC[networkId]],
|
|
153
|
-
modules: [module],
|
|
154
|
-
});
|
|
155
|
-
const wallet = await selector.wallet(id);
|
|
156
|
-
const accounts = await signInAccountOnly(wallet);
|
|
157
|
-
const account = q.account || accounts[0]?.accountId;
|
|
158
|
-
if (!account || !accounts.some((a) => a.accountId === account))
|
|
159
|
-
throw new Error('Select the requested NEAR account.');
|
|
160
|
-
if (q.operation === 'near.nep413') {
|
|
161
|
-
if (!wallet.signMessage)
|
|
162
|
-
throw new Error('NEP-413 unavailable');
|
|
163
|
-
const p = q.payload;
|
|
164
|
-
const state = crypto.randomUUID();
|
|
165
|
-
callbacks.set(state, q.id);
|
|
166
|
-
const signed = await wallet.signMessage({
|
|
167
|
-
...p,
|
|
168
|
-
nonce: Buffer.from(p.nonce),
|
|
169
|
-
state,
|
|
170
|
-
});
|
|
171
|
-
if (!signed)
|
|
172
|
-
return; // redirect wallets: /callback forwards the fragment
|
|
173
|
-
callbacks.delete(state);
|
|
174
|
-
result = signed;
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
const p = q.payload;
|
|
178
|
-
const outcome = await wallet.signAndSendTransaction({
|
|
179
|
-
signerId: account,
|
|
180
|
-
receiverId: p.receiverId,
|
|
181
|
-
// Selector v10 passes Borsh Action structs through to its transaction encoder.
|
|
182
|
-
actions: [
|
|
183
|
-
{
|
|
184
|
-
functionCall: {
|
|
185
|
-
methodName: p.method,
|
|
186
|
-
args: Buffer.from(JSON.stringify(p.args)),
|
|
187
|
-
gas: BigInt(p.gas),
|
|
188
|
-
deposit: BigInt(p.deposit),
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
});
|
|
193
|
-
if (!outcome)
|
|
194
|
-
return;
|
|
195
|
-
result = { txHash: outcome.transaction.hash };
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
await api('/api/result', { id: q.id, result });
|
|
199
|
-
status.textContent = 'Verified. Return to the terminal.';
|
|
191
|
+
const outcome = await wallets.run(q, walletId());
|
|
192
|
+
busy = false;
|
|
193
|
+
if (outcome.kind === 'connected')
|
|
194
|
+
status(connected(outcome.account));
|
|
195
|
+
else if (outcome.kind === 'result')
|
|
196
|
+
await submit(q.id, outcome.result);
|
|
200
197
|
}
|
|
201
198
|
catch (e) {
|
|
202
|
-
|
|
199
|
+
// One click per attempt, never retried here: the person may try this request again until it
|
|
200
|
+
// expires. The CLI hears only a fixed reason name.
|
|
201
|
+
const failure = walletFailure(e);
|
|
202
|
+
status({ tone: 'error', text: failure.text });
|
|
203
|
+
void api('/api/report', { id: q.id, reason: failure.reason }).catch(() => { });
|
|
203
204
|
}
|
|
204
205
|
finally {
|
|
205
|
-
|
|
206
|
+
busy = false;
|
|
207
|
+
controls();
|
|
206
208
|
}
|
|
207
209
|
};
|
|
210
|
+
if (cancel)
|
|
211
|
+
cancel.onclick = () => {
|
|
212
|
+
const q = current;
|
|
213
|
+
if (!q || busy || done.has(q.id))
|
|
214
|
+
return;
|
|
215
|
+
done.add(q.id);
|
|
216
|
+
controls();
|
|
217
|
+
status({ tone: 'error', text: 'Cancelled. Return to the terminal.' });
|
|
218
|
+
void api('/api/report', { id: q.id, reason: 'cancelled' }).catch(() => { });
|
|
219
|
+
};
|
|
208
220
|
if (!redirected) {
|
|
209
221
|
void (async () => {
|
|
210
222
|
if (!capability)
|
|
211
223
|
capability = (await api('/api/bootstrap')).capability;
|
|
212
224
|
await poll();
|
|
213
225
|
})().catch((e) => {
|
|
214
|
-
status
|
|
226
|
+
status({ tone: 'error', text: e.message });
|
|
215
227
|
});
|
|
216
228
|
}
|