@defdone/annon 0.1.0-alpha.1 → 0.1.0-alpha.2
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 +8 -0
- package/CLI.md +116 -10
- package/README.md +41 -9
- 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 +67 -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-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 +115 -140
- 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.js +5 -2
- 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 +67 -13
- package/dist/node/buy-commands.js +9 -2
- package/dist/node/cli.js +23 -7
- package/dist/node/command-context.js +1 -1
- package/dist/node/config.d.ts +2 -0
- package/dist/node/config.js +3 -1
- package/dist/node/login-page.d.ts +7 -0
- package/dist/node/login-page.js +71 -0
- package/dist/node/purchase-commands.d.ts +2 -2
- package/dist/node/purchase-commands.js +10 -6
- package/dist/node/saved-state.d.ts +4 -0
- package/dist/node/saved-state.js +50 -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
|
@@ -3,6 +3,8 @@ import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { createServer } from 'node:http';
|
|
5
5
|
import { TransactionUncertain } from '../wallet/uncertain.js';
|
|
6
|
+
import { APPROVED_LINE, cancelledError, expiredError, failureLine, isPageReport, openingLine, sendUncertain, usableHash, waitingLine, } from './approval-status.js';
|
|
7
|
+
import { LOGIN_PAGE_CSS, LOGIN_PAGE_HTML } from './login-page.js';
|
|
6
8
|
const equal = (a, b) => Buffer.byteLength(a) === Buffer.byteLength(b) && timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
7
9
|
/**
|
|
8
10
|
* The approval page talks only to this loopback server, the NEAR RPC its Wallet Selector uses
|
|
@@ -12,6 +14,7 @@ const equal = (a, b) => Buffer.byteLength(a) === Buffer.byteLength(b) && timingS
|
|
|
12
14
|
*/
|
|
13
15
|
export const LOGIN_PAGE_CSP = "default-src 'none'; script-src 'self'; connect-src 'self' https://free.rpc.fastnear.com https://test.rpc.fastnear.com https://h4n.app; frame-src https://hot-labs.org; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; form-action 'none'";
|
|
14
16
|
export async function startBrowserSession(o) {
|
|
17
|
+
const notify = o.notify ?? ((line) => void process.stderr.write(`${line}\n`));
|
|
15
18
|
let asset = o.asset;
|
|
16
19
|
if (asset === undefined) {
|
|
17
20
|
try {
|
|
@@ -41,6 +44,15 @@ export async function startBrowserSession(o) {
|
|
|
41
44
|
.map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`)
|
|
42
45
|
.join(',')}}`
|
|
43
46
|
: JSON.stringify(v);
|
|
47
|
+
/** Ends a request whose transaction may be on chain, with the CLI's own warning. */
|
|
48
|
+
const uncertain = (p, txHash) => {
|
|
49
|
+
const { line, error } = sendUncertain(p.action, txHash);
|
|
50
|
+
notify(line);
|
|
51
|
+
return error;
|
|
52
|
+
};
|
|
53
|
+
/** A transaction request whose send started, or that returned transaction evidence at all. */
|
|
54
|
+
const sent = (p) => p.sending === true ||
|
|
55
|
+
p.action.operation === 'near.signTransaction';
|
|
44
56
|
let pending;
|
|
45
57
|
const server = createServer((req, res) => {
|
|
46
58
|
void (async () => {
|
|
@@ -67,8 +79,12 @@ export async function startBrowserSession(o) {
|
|
|
67
79
|
!equal(req.headers.authorization ?? '', `Bearer ${capability}`))
|
|
68
80
|
return void send(403, '{}');
|
|
69
81
|
if (path === '/api/action' && ['GET', 'POST'].includes(req.method ?? ''))
|
|
70
|
-
return void send(200, JSON.stringify({
|
|
71
|
-
|
|
82
|
+
return void send(200, JSON.stringify({
|
|
83
|
+
pairingCode,
|
|
84
|
+
action: pending?.action ?? null,
|
|
85
|
+
sendStarted: pending?.sending === true,
|
|
86
|
+
}));
|
|
87
|
+
if (!['/api/result', '/api/report'].includes(path) || req.method !== 'POST')
|
|
72
88
|
return void send(404, '{}');
|
|
73
89
|
let size = 0;
|
|
74
90
|
const parts = [];
|
|
@@ -79,6 +95,31 @@ export async function startBrowserSession(o) {
|
|
|
79
95
|
parts.push(chunk);
|
|
80
96
|
}
|
|
81
97
|
const b = JSON.parse(Buffer.concat(parts).toString());
|
|
98
|
+
if (path === '/api/report') {
|
|
99
|
+
// A fixed reason for the current, unconsumed request. Only `cancelled` and
|
|
100
|
+
// `send_uncertain` end it, and never as cancelled once the page started a send.
|
|
101
|
+
const p = pending;
|
|
102
|
+
if (!p || p.busy || b.id !== p.action.id || !isPageReport(b.reason))
|
|
103
|
+
return void send(409, '{}');
|
|
104
|
+
if (b.reason === 'send_started') {
|
|
105
|
+
// One send per request: a reloaded or second page cannot start another.
|
|
106
|
+
if (p.sending)
|
|
107
|
+
return void send(409, '{}');
|
|
108
|
+
p.sending = true;
|
|
109
|
+
}
|
|
110
|
+
else if (b.reason === 'cancelled' || b.reason === 'send_uncertain') {
|
|
111
|
+
clearTimeout(p.timer);
|
|
112
|
+
pending = undefined;
|
|
113
|
+
armIdle();
|
|
114
|
+
p.reject(p.sending || b.reason === 'send_uncertain' ? uncertain(p) : cancelledError());
|
|
115
|
+
}
|
|
116
|
+
else if (!p.sending && p.failure !== b.reason) {
|
|
117
|
+
// After a send started no failure can be retried, so none is announced as one.
|
|
118
|
+
p.failure = b.reason;
|
|
119
|
+
notify(failureLine(b.reason, p.action.expiresAt - Date.now()));
|
|
120
|
+
}
|
|
121
|
+
return void send(200, '{}');
|
|
122
|
+
}
|
|
82
123
|
const key = canonical(b.result);
|
|
83
124
|
const previous = results.get(b.id);
|
|
84
125
|
if (previous && Date.now() < previous.expiresAt) {
|
|
@@ -104,6 +145,7 @@ export async function startBrowserSession(o) {
|
|
|
104
145
|
pending = undefined;
|
|
105
146
|
armIdle();
|
|
106
147
|
}
|
|
148
|
+
notify(APPROVED_LINE);
|
|
107
149
|
p.resolve(b.result);
|
|
108
150
|
complete(200);
|
|
109
151
|
send(200, '{}');
|
|
@@ -114,7 +156,13 @@ export async function startBrowserSession(o) {
|
|
|
114
156
|
pending = undefined;
|
|
115
157
|
armIdle();
|
|
116
158
|
}
|
|
117
|
-
|
|
159
|
+
// After a send, a result that cannot be verified is never evidence of success or of
|
|
160
|
+
// failure: the request ends as uncertain, with any usable hash, so it stays saved.
|
|
161
|
+
p.reject(sent(p)
|
|
162
|
+
? uncertain(p, usableHash(error instanceof TransactionUncertain ? error.txHash : b?.result?.txHash))
|
|
163
|
+
: error instanceof TransactionUncertain
|
|
164
|
+
? error
|
|
165
|
+
: new Error('wallet result rejected'));
|
|
118
166
|
complete(400);
|
|
119
167
|
send(400, '{}');
|
|
120
168
|
}
|
|
@@ -124,8 +172,10 @@ export async function startBrowserSession(o) {
|
|
|
124
172
|
return void send(405, '{}');
|
|
125
173
|
if (path === '/app.js')
|
|
126
174
|
return void send(200, asset, 'text/javascript');
|
|
175
|
+
if (path === '/app.css')
|
|
176
|
+
return void send(200, LOGIN_PAGE_CSS, 'text/css; charset=utf-8');
|
|
127
177
|
if (path === '/' || path === '/callback')
|
|
128
|
-
return void send(200,
|
|
178
|
+
return void send(200, LOGIN_PAGE_HTML, 'text/html; charset=utf-8');
|
|
129
179
|
send(404, '{}');
|
|
130
180
|
})().catch(() => {
|
|
131
181
|
if (!res.headersSent)
|
|
@@ -161,6 +211,9 @@ export async function startBrowserSession(o) {
|
|
|
161
211
|
};
|
|
162
212
|
armIdle();
|
|
163
213
|
const url = `${origin}/#${capability}`;
|
|
214
|
+
// The capability-free page URL: in open mode the page bootstraps the capability once.
|
|
215
|
+
if (!o.noOpen)
|
|
216
|
+
notify(openingLine(`${origin}/`));
|
|
164
217
|
if (!o.noOpen)
|
|
165
218
|
(o.open ??
|
|
166
219
|
((target) => {
|
|
@@ -183,21 +236,22 @@ export async function startBrowserSession(o) {
|
|
|
183
236
|
return;
|
|
184
237
|
}
|
|
185
238
|
clearTimeout(timeout);
|
|
186
|
-
const
|
|
187
|
-
if (pending?.timer === timer) {
|
|
188
|
-
pending = undefined;
|
|
189
|
-
armIdle();
|
|
190
|
-
}
|
|
191
|
-
reject(new Error('action expired'));
|
|
192
|
-
}, action.expiresAt - Date.now());
|
|
193
|
-
pending = {
|
|
239
|
+
const entry = {
|
|
194
240
|
action: JSON.parse(JSON.stringify(action)),
|
|
195
241
|
busy: false,
|
|
196
242
|
resolve: resolve,
|
|
197
243
|
reject,
|
|
198
244
|
verify: verify,
|
|
199
|
-
timer
|
|
245
|
+
timer: setTimeout(() => {
|
|
246
|
+
if (pending === entry) {
|
|
247
|
+
pending = undefined;
|
|
248
|
+
armIdle();
|
|
249
|
+
}
|
|
250
|
+
reject(entry.sending ? uncertain(entry) : expiredError(entry.failure));
|
|
251
|
+
}, action.expiresAt - Date.now()),
|
|
200
252
|
};
|
|
253
|
+
pending = entry;
|
|
254
|
+
notify(waitingLine(action.expiresAt - Date.now()));
|
|
201
255
|
}),
|
|
202
256
|
};
|
|
203
257
|
}
|
|
@@ -176,6 +176,10 @@ async function buy(ctx, preparedBuy) {
|
|
|
176
176
|
return true;
|
|
177
177
|
if (!saved)
|
|
178
178
|
return Date.now() / 1000 < claimUntil;
|
|
179
|
+
// A NEAR send that may be on chain blocks another payment until reconciled or --force,
|
|
180
|
+
// even after the purchase expired: a late transfer still moves tokens.
|
|
181
|
+
if (saved.nearSubmission?.state === 'uncertain')
|
|
182
|
+
return true;
|
|
179
183
|
return (Number(saved.purchase.expires_at) > Date.now() / 1000 ||
|
|
180
184
|
(saved.purchase.route === 'base-usdc' && saved.attempts.length > 0));
|
|
181
185
|
},
|
|
@@ -277,11 +281,14 @@ async function buy(ctx, preparedBuy) {
|
|
|
277
281
|
: bought.instructions
|
|
278
282
|
? 'deposit_required'
|
|
279
283
|
: 'submitted',
|
|
280
|
-
|
|
284
|
+
// A browser send that failed after the wallet's send call has no hash to report.
|
|
285
|
+
...(bought.uncertain?.txHash ? { txHash: bought.uncertain.txHash } : {}),
|
|
281
286
|
backup: backupPath(c, bought.record.id),
|
|
282
287
|
...(bought.instructions ? { instructions: bought.instructions } : {}),
|
|
283
288
|
...(source ? { source } : {}),
|
|
284
|
-
next:
|
|
289
|
+
next: bought.uncertain
|
|
290
|
+
? `The wallet may have sent this payment. Do not pay again. Run annon status ${bought.record.id} until it is accepted.`
|
|
291
|
+
: 'Fund the deposit if instructed, then check status and open this purchase.',
|
|
285
292
|
};
|
|
286
293
|
}
|
|
287
294
|
catch (error) {
|
package/dist/node/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
2
|
import { publicError, usage } from './errors.js';
|
|
3
|
+
import { version } from './version.js';
|
|
3
4
|
const common = [
|
|
4
5
|
'no-open',
|
|
5
6
|
'json',
|
|
@@ -57,6 +58,7 @@ const perCommand = {
|
|
|
57
58
|
verify: [],
|
|
58
59
|
status: [],
|
|
59
60
|
};
|
|
61
|
+
const bareInit = ['json', 'config', 'stateDir', 'backupSecretFile'];
|
|
60
62
|
export function parseCli(argv) {
|
|
61
63
|
try {
|
|
62
64
|
const keys = [...new Set([...common, ...Object.values(perCommand).flat()])];
|
|
@@ -117,7 +119,10 @@ export function parseCli(argv) {
|
|
|
117
119
|
else if (!values.price || !values.split)
|
|
118
120
|
throw usage();
|
|
119
121
|
}
|
|
120
|
-
|
|
122
|
+
// Bare init only sets up the backup secret; signer and deployment options need --agent.
|
|
123
|
+
if (cmd === 'init' &&
|
|
124
|
+
values.agent !== true &&
|
|
125
|
+
Object.keys(values).some((k) => !bareInit.includes(k)))
|
|
121
126
|
throw usage();
|
|
122
127
|
if (cmd === 'recover' && !values.recovery)
|
|
123
128
|
throw usage();
|
|
@@ -136,10 +141,12 @@ export function parseCli(argv) {
|
|
|
136
141
|
throw usage();
|
|
137
142
|
}
|
|
138
143
|
}
|
|
144
|
+
const versionFlags = ['--version', '-v'];
|
|
139
145
|
const HELP = `annon <command> [options]
|
|
140
146
|
shops [--search <q>] [--limit <n>] [--offset <n>]
|
|
141
147
|
shop add <url> [--id <alias>]
|
|
142
148
|
catalog [--shop <id>] [--search <q>] [--limit <n>] [--offset <n>]
|
|
149
|
+
init [--backup-secret-file <file>] [--state-dir <dir>] [--config <file>]
|
|
143
150
|
login [--wallet evm|near] [--no-open]
|
|
144
151
|
init --agent --daily-budget <USDC> [--recovery <kind:id>] [--key-env <name> | --keychain-entry <name> | --new-key | --signer <https origin> (--credential-env <name> | --credential-keychain <name>)]
|
|
145
152
|
share <file> --price <USDC> [--shop <id>] [--title <title>] [--description <text>] [--listing-storage <NEAR>] [--storage-budget <NEAR>]
|
|
@@ -158,9 +165,11 @@ Common: --json --config <file> --contract-id <account> --near-rpc <url> --base-r
|
|
|
158
165
|
--network <near:mainnet> --state-dir <dir> --relayer <url> --trust-contract
|
|
159
166
|
--wallet evm|solana|near --evm-key-file <file> --solana-key-file <file> --near-key-file <file>
|
|
160
167
|
--near-account <account> --near-public-key <ed25519:key> --backup-secret-file <file>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
168
|
+
Backup secret: run init once; it creates ~/.config/annon/backup-secret (owner-only) and never
|
|
169
|
+
overwrites. --backup-secret-file or ANNON_BACKUP_SECRET_FILE, then ANNON_BACKUP_SECRET, override it.
|
|
170
|
+
Secrets: env ANNON_EVM_PRIVATE_KEY, ANNON_SOLANA_KEYPAIR, ANNON_NEAR_CREDENTIALS; or explicitly
|
|
171
|
+
named files. See packages/annon/CLI.md. --version prints the installed version.
|
|
172
|
+
Exit: 0 success (buy may need deposit), 1 operation failed, 2 arguments/config, 3 trust required, 4 purchase pending or uncertain.
|
|
164
173
|
`;
|
|
165
174
|
export async function runCli(argv, execute, write) {
|
|
166
175
|
const json = argv.includes('--json') || argv.includes('--json=true');
|
|
@@ -168,6 +177,12 @@ export async function runCli(argv, execute, write) {
|
|
|
168
177
|
write(HELP);
|
|
169
178
|
return 0;
|
|
170
179
|
}
|
|
180
|
+
// Answered before any config, agent policy, wallet, network or backup-secret step.
|
|
181
|
+
if (argv.some((a) => versionFlags.includes(a)) &&
|
|
182
|
+
argv.every((a) => [...versionFlags, '--json'].includes(a))) {
|
|
183
|
+
write(json ? JSON.stringify({ ok: true, result: { version } }) : version);
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
171
186
|
try {
|
|
172
187
|
const input = parseCli(argv);
|
|
173
188
|
const result = await execute(input);
|
|
@@ -185,11 +200,12 @@ export async function runCli(argv, execute, write) {
|
|
|
185
200
|
show(item);
|
|
186
201
|
}
|
|
187
202
|
write(JSON.stringify({ ok: true, result }, null, json ? undefined : 2));
|
|
188
|
-
|
|
189
|
-
|
|
203
|
+
// 4: not settled. A pending status, or a buy whose payment may be on chain (uncertain).
|
|
204
|
+
return result &&
|
|
190
205
|
typeof result === 'object' &&
|
|
191
206
|
'state' in result &&
|
|
192
|
-
result.state === 'pending'
|
|
207
|
+
((input.command === 'status' && result.state === 'pending') ||
|
|
208
|
+
(input.command === 'buy' && result.state === 'uncertain'))
|
|
193
209
|
? 4
|
|
194
210
|
: 0;
|
|
195
211
|
}
|
|
@@ -65,7 +65,7 @@ async function pendingSetup(ctx, purchaseId) {
|
|
|
65
65
|
return {
|
|
66
66
|
purchaseId,
|
|
67
67
|
state: 'pending',
|
|
68
|
-
next: 'Check the payment
|
|
68
|
+
next: 'Check the payment. To resume Base unlock, keep the saved state readable and restore its matching backup secret (or select its existing copy with ANNON_BACKUP_SECRET_FILE). 1Click SUCCESS alone is not acceptance.',
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
/** Loads the command's wallet once; later calls reuse it. */
|
package/dist/node/config.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface Config extends DiscoveryConfig {
|
|
|
32
32
|
bps: number;
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
/** Human default for saved records and backups; agent and explicit settings override it. */
|
|
36
|
+
export declare const defaultStateDir: () => string;
|
|
35
37
|
export declare function loadConfig(o: Input, env: Environment, login?: boolean, forcedAgent?: boolean): Promise<Config>;
|
|
36
38
|
export type TrustPrompt = (description: string) => Promise<boolean>;
|
|
37
39
|
export declare function trustContract(c: Config, explicit: boolean, prompt?: TrustPrompt): Promise<void>;
|
package/dist/node/config.js
CHANGED
|
@@ -8,6 +8,8 @@ import { validateDiscoveryConfig } from '../shops/discovery.js';
|
|
|
8
8
|
import { agentPolicy, configPath, validateSigner } from './agent.js';
|
|
9
9
|
import { AdapterError } from './errors.js';
|
|
10
10
|
import { resourceAllowed } from './resources.js';
|
|
11
|
+
/** Human default for saved records and backups; agent and explicit settings override it. */
|
|
12
|
+
export const defaultStateDir = () => join(homedir(), '.local', 'share', 'annon');
|
|
11
13
|
export async function loadConfig(o, env, login = false, forcedAgent = false) {
|
|
12
14
|
const pinnedAgent = await agentPolicy(o, env, forcedAgent);
|
|
13
15
|
try {
|
|
@@ -41,7 +43,7 @@ export async function loadConfig(o, env, login = false, forcedAgent = false) {
|
|
|
41
43
|
stateDir: o.stateDir ??
|
|
42
44
|
(agent ? undefined : env.ANNON_STATE_DIR) ??
|
|
43
45
|
file.stateDir ??
|
|
44
|
-
|
|
46
|
+
defaultStateDir(),
|
|
45
47
|
};
|
|
46
48
|
Object.assign(c, validateDiscoveryConfig(file));
|
|
47
49
|
const workDir = (env.ANNON_AGENT === 'true' ? env.ANNON_WORK_DIR : undefined) ?? file.workDir;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval page shell and its stylesheet. Both are static: the page script fills in every
|
|
3
|
+
* request value as text. The stylesheet is served from the page's own origin (`style-src 'self'`),
|
|
4
|
+
* with no inline styles, web fonts or other resources.
|
|
5
|
+
*/
|
|
6
|
+
export declare const LOGIN_PAGE_HTML = "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"light\"><title>Annon wallet approval</title><link rel=\"stylesheet\" href=\"/app.css\"><script src=\"/app.js\" defer></script></head><body><main class=\"card\">\n<header class=\"brand\"><svg class=\"mark\" viewBox=\"0 0 24 28\" width=\"22\" height=\"26\" aria-hidden=\"true\"><path d=\"M2 27V12a10 10 0 0 1 20 0v15h-5V12a5 5 0 0 0-10 0v15z\"/></svg><p class=\"eyebrow\">ANNON CLI</p></header>\n<h1 id=\"title\">Waiting for the CLI</h1>\n<section class=\"pair\" aria-labelledby=\"pair-label\"><p class=\"label\" id=\"pair-label\">Pairing code</p><p id=\"pair\" class=\"code\">------</p><p class=\"hint\">Check that your terminal shows the same code.</p></section>\n<section id=\"summary\" class=\"summary\" hidden><p id=\"amount\" class=\"amount\" hidden></p><dl id=\"rows\" class=\"rows\"></dl><div id=\"message-box\" class=\"message\" hidden><p id=\"message-label\" class=\"label\"></p><pre id=\"message\"></pre></div><ul id=\"warnings\" class=\"warnings\" hidden></ul><div id=\"terms-box\" class=\"terms\" hidden><p class=\"label\">Terms from the CLI</p><p class=\"hint\">For review. Your wallet signs only the request above.</p><dl id=\"terms\" class=\"rows\"></dl></div><p id=\"note\" class=\"note\"></p></section>\n<section class=\"act\"><div id=\"wallet-field\" class=\"field\" hidden><span class=\"label\" id=\"near-wallet-label\">NEAR wallet</span><div class=\"picker\"><div id=\"near-wallet\" class=\"picker-button\" role=\"combobox\" tabindex=\"0\" aria-haspopup=\"listbox\" aria-expanded=\"false\" aria-controls=\"near-wallet-list\" aria-labelledby=\"near-wallet-label near-wallet-value\"><span id=\"near-wallet-value\">Meteor Wallet</span></div><ul id=\"near-wallet-list\" class=\"picker-list\" role=\"listbox\" aria-labelledby=\"near-wallet-label\" hidden><li id=\"near-wallet-meteor\" class=\"option\" role=\"option\" aria-selected=\"true\">Meteor Wallet<span class=\"option-note\">Extension or web popup</span></li><li id=\"near-wallet-hot\" class=\"option\" role=\"option\" aria-selected=\"false\">HOT Wallet<span class=\"option-note\">Mainnet only</span></li></ul></div></div><div class=\"buttons\"><button id=\"approve\" class=\"primary\" disabled>Review in wallet</button><button id=\"cancel\" class=\"secondary\" hidden>Cancel</button></div><p id=\"status\" class=\"status\" role=\"status\" aria-live=\"polite\"></p></section>\n<details class=\"details\"><summary>Technical details</summary><pre id=\"action\"></pre></details>\n</main></body></html>";
|
|
7
|
+
export declare const LOGIN_PAGE_CSS = ":root{--paper:#FAF8F2;--alt:#F1EDE3;--ink:#111111;--soft:#2E2C28;--muted:#4A4741;--line:#D9D3C7;--red:#B3261E;--serif:Newsreader,\"Iowan Old Style\",Georgia,serif;--mono:\"IBM Plex Mono\",ui-monospace,\"SF Mono\",Menlo,monospace}\n*{box-sizing:border-box;border-radius:0}\nhtml{background:var(--paper)}\nbody{margin:0;background:var(--paper);color:var(--ink);font:16px/1.5 var(--serif);-webkit-text-size-adjust:100%}\n.card{max-width:640px;margin:0 auto;padding:40px 16px 56px}\n.brand{display:flex;align-items:center;gap:10px;margin-bottom:20px}\n.mark{fill:var(--ink);flex:none}\n.eyebrow,.label,dt,.details summary,button{font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;font-size:12px}\n.eyebrow{margin:0;color:var(--soft)}\nh1{font-weight:500;font-size:clamp(28px,6vw,36px);line-height:1.15;margin:0 0 24px;letter-spacing:-.01em}\n.label{margin:0 0 6px;color:var(--muted)}\n.hint{margin:6px 0 0;color:var(--muted);font-size:15px}\n.pair{border:1px solid var(--ink);background:var(--alt);padding:16px 20px;margin-bottom:28px}\n.code{margin:0;font:600 clamp(30px,9vw,40px)/1.1 var(--mono);letter-spacing:.18em;word-break:break-all}\n.summary{border-top:1px solid var(--line);padding-top:20px}\n.amount{margin:0 0 12px;font-size:clamp(30px,8vw,42px);line-height:1.1}\n.rows{margin:0}\n.row{display:grid;grid-template-columns:minmax(0,11em) minmax(0,1fr);gap:4px 16px;padding:9px 0;border-bottom:1px solid var(--line)}\ndt{color:var(--muted);padding-top:3px}\ndd{margin:0;color:var(--soft);overflow-wrap:anywhere}\ndd.mono{font:14px/1.5 var(--mono)}\n.message,.terms{margin-top:20px}\npre{margin:0;padding:14px 16px;background:var(--alt);border:1px solid var(--line);font:13px/1.55 var(--mono);color:var(--soft);white-space:pre-wrap;overflow-wrap:anywhere;max-height:22em;overflow:auto}\n.warnings{margin:20px 0 0;padding:12px 16px 12px 32px;border:1px solid var(--red);color:var(--red)}\n.note{margin:20px 0 0;color:var(--soft)}\n.act{margin-top:28px;border-top:1px solid var(--line);padding-top:20px}\n.field{margin:0 0 16px;display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px}\n.picker{position:relative;flex:0 1 20em;min-width:0}\n.picker-button{display:flex;align-items:center;justify-content:space-between;gap:12px;background:var(--paper);color:var(--ink);border:1px solid var(--ink);padding:10px 12px;cursor:pointer;font:14px/1.4 var(--mono);user-select:none}\n.picker-button::after{content:\"\";width:0;height:0;border:5px solid transparent;border-top-color:var(--ink);border-bottom:0;flex:none}\n.picker-button[aria-expanded=\"true\"]::after{border-top:0;border-bottom:5px solid var(--ink)}\n.picker-button[aria-disabled=\"true\"]{opacity:.4;cursor:not-allowed}\n.picker-list{position:absolute;z-index:2;left:0;right:0;top:100%;margin:0;padding:0;list-style:none;background:var(--paper);border:1px solid var(--ink);border-top:0}\n.option{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:10px 12px;cursor:pointer;font:14px/1.4 var(--mono);color:var(--soft)}\n.option+.option{border-top:1px solid var(--line)}\n.option.active{background:var(--alt);color:var(--ink)}\n.option[aria-selected=\"true\"]{color:var(--ink);font-weight:600}\n.option[aria-selected=\"true\"]::before{content:\"\";width:6px;height:6px;background:var(--ink);flex:none;align-self:center}\n.option[aria-disabled=\"true\"]{color:var(--muted);cursor:not-allowed;opacity:.6}\n.option-note{margin-left:auto;font-size:11px;font-weight:400;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}\n.buttons{display:flex;flex-wrap:wrap;gap:12px}\nbutton{padding:14px 22px;border:1px solid var(--ink);cursor:pointer}\n.primary{background:var(--ink);color:var(--paper)}\n.secondary{background:transparent;color:var(--ink)}\nbutton:disabled{cursor:not-allowed;opacity:.4}\nbutton:focus-visible,.picker-button:focus-visible,summary:focus-visible{outline:2px solid var(--red);outline-offset:2px}\n.status{min-height:1.5em;margin:16px 0 0;color:var(--soft)}\n.status.wait{color:var(--muted)}\n.status.ok{color:var(--ink);border-left:3px solid var(--ink);padding-left:12px}\n.status.error{color:var(--red);border-left:3px solid var(--red);padding-left:12px}\n.details{margin-top:32px;border-top:1px solid var(--line);padding-top:12px}\n.details summary{cursor:pointer;color:var(--muted);padding:6px 0}\n.details pre{margin-top:10px}\n[hidden]{display:none!important}\n@media (max-width:480px){.row{grid-template-columns:1fr}.card{padding-top:28px}button{flex:1 1 100%}.picker{flex-basis:100%}}\n";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval page shell and its stylesheet. Both are static: the page script fills in every
|
|
3
|
+
* request value as text. The stylesheet is served from the page's own origin (`style-src 'self'`),
|
|
4
|
+
* with no inline styles, web fonts or other resources.
|
|
5
|
+
*/
|
|
6
|
+
/** A small arch: the Annon mark. Inline SVG, so it needs no image source. */
|
|
7
|
+
const MARK = '<svg class="mark" viewBox="0 0 24 28" width="22" height="26" aria-hidden="true"><path d="M2 27V12a10 10 0 0 1 20 0v15h-5V12a5 5 0 0 0-10 0v15z"/></svg>';
|
|
8
|
+
export const LOGIN_PAGE_HTML = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="color-scheme" content="light"><title>Annon wallet approval</title><link rel="stylesheet" href="/app.css"><script src="/app.js" defer></script></head><body><main class="card">
|
|
9
|
+
<header class="brand">${MARK}<p class="eyebrow">ANNON CLI</p></header>
|
|
10
|
+
<h1 id="title">Waiting for the CLI</h1>
|
|
11
|
+
<section class="pair" aria-labelledby="pair-label"><p class="label" id="pair-label">Pairing code</p><p id="pair" class="code">------</p><p class="hint">Check that your terminal shows the same code.</p></section>
|
|
12
|
+
<section id="summary" class="summary" hidden><p id="amount" class="amount" hidden></p><dl id="rows" class="rows"></dl><div id="message-box" class="message" hidden><p id="message-label" class="label"></p><pre id="message"></pre></div><ul id="warnings" class="warnings" hidden></ul><div id="terms-box" class="terms" hidden><p class="label">Terms from the CLI</p><p class="hint">For review. Your wallet signs only the request above.</p><dl id="terms" class="rows"></dl></div><p id="note" class="note"></p></section>
|
|
13
|
+
<section class="act"><div id="wallet-field" class="field" hidden><span class="label" id="near-wallet-label">NEAR wallet</span><div class="picker"><div id="near-wallet" class="picker-button" role="combobox" tabindex="0" aria-haspopup="listbox" aria-expanded="false" aria-controls="near-wallet-list" aria-labelledby="near-wallet-label near-wallet-value"><span id="near-wallet-value">Meteor Wallet</span></div><ul id="near-wallet-list" class="picker-list" role="listbox" aria-labelledby="near-wallet-label" hidden><li id="near-wallet-meteor" class="option" role="option" aria-selected="true">Meteor Wallet<span class="option-note">Extension or web popup</span></li><li id="near-wallet-hot" class="option" role="option" aria-selected="false">HOT Wallet<span class="option-note">Mainnet only</span></li></ul></div></div><div class="buttons"><button id="approve" class="primary" disabled>Review in wallet</button><button id="cancel" class="secondary" hidden>Cancel</button></div><p id="status" class="status" role="status" aria-live="polite"></p></section>
|
|
14
|
+
<details class="details"><summary>Technical details</summary><pre id="action"></pre></details>
|
|
15
|
+
</main></body></html>`;
|
|
16
|
+
export const LOGIN_PAGE_CSS = `:root{--paper:#FAF8F2;--alt:#F1EDE3;--ink:#111111;--soft:#2E2C28;--muted:#4A4741;--line:#D9D3C7;--red:#B3261E;--serif:Newsreader,"Iowan Old Style",Georgia,serif;--mono:"IBM Plex Mono",ui-monospace,"SF Mono",Menlo,monospace}
|
|
17
|
+
*{box-sizing:border-box;border-radius:0}
|
|
18
|
+
html{background:var(--paper)}
|
|
19
|
+
body{margin:0;background:var(--paper);color:var(--ink);font:16px/1.5 var(--serif);-webkit-text-size-adjust:100%}
|
|
20
|
+
.card{max-width:640px;margin:0 auto;padding:40px 16px 56px}
|
|
21
|
+
.brand{display:flex;align-items:center;gap:10px;margin-bottom:20px}
|
|
22
|
+
.mark{fill:var(--ink);flex:none}
|
|
23
|
+
.eyebrow,.label,dt,.details summary,button{font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;font-size:12px}
|
|
24
|
+
.eyebrow{margin:0;color:var(--soft)}
|
|
25
|
+
h1{font-weight:500;font-size:clamp(28px,6vw,36px);line-height:1.15;margin:0 0 24px;letter-spacing:-.01em}
|
|
26
|
+
.label{margin:0 0 6px;color:var(--muted)}
|
|
27
|
+
.hint{margin:6px 0 0;color:var(--muted);font-size:15px}
|
|
28
|
+
.pair{border:1px solid var(--ink);background:var(--alt);padding:16px 20px;margin-bottom:28px}
|
|
29
|
+
.code{margin:0;font:600 clamp(30px,9vw,40px)/1.1 var(--mono);letter-spacing:.18em;word-break:break-all}
|
|
30
|
+
.summary{border-top:1px solid var(--line);padding-top:20px}
|
|
31
|
+
.amount{margin:0 0 12px;font-size:clamp(30px,8vw,42px);line-height:1.1}
|
|
32
|
+
.rows{margin:0}
|
|
33
|
+
.row{display:grid;grid-template-columns:minmax(0,11em) minmax(0,1fr);gap:4px 16px;padding:9px 0;border-bottom:1px solid var(--line)}
|
|
34
|
+
dt{color:var(--muted);padding-top:3px}
|
|
35
|
+
dd{margin:0;color:var(--soft);overflow-wrap:anywhere}
|
|
36
|
+
dd.mono{font:14px/1.5 var(--mono)}
|
|
37
|
+
.message,.terms{margin-top:20px}
|
|
38
|
+
pre{margin:0;padding:14px 16px;background:var(--alt);border:1px solid var(--line);font:13px/1.55 var(--mono);color:var(--soft);white-space:pre-wrap;overflow-wrap:anywhere;max-height:22em;overflow:auto}
|
|
39
|
+
.warnings{margin:20px 0 0;padding:12px 16px 12px 32px;border:1px solid var(--red);color:var(--red)}
|
|
40
|
+
.note{margin:20px 0 0;color:var(--soft)}
|
|
41
|
+
.act{margin-top:28px;border-top:1px solid var(--line);padding-top:20px}
|
|
42
|
+
.field{margin:0 0 16px;display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px}
|
|
43
|
+
.picker{position:relative;flex:0 1 20em;min-width:0}
|
|
44
|
+
.picker-button{display:flex;align-items:center;justify-content:space-between;gap:12px;background:var(--paper);color:var(--ink);border:1px solid var(--ink);padding:10px 12px;cursor:pointer;font:14px/1.4 var(--mono);user-select:none}
|
|
45
|
+
.picker-button::after{content:"";width:0;height:0;border:5px solid transparent;border-top-color:var(--ink);border-bottom:0;flex:none}
|
|
46
|
+
.picker-button[aria-expanded="true"]::after{border-top:0;border-bottom:5px solid var(--ink)}
|
|
47
|
+
.picker-button[aria-disabled="true"]{opacity:.4;cursor:not-allowed}
|
|
48
|
+
.picker-list{position:absolute;z-index:2;left:0;right:0;top:100%;margin:0;padding:0;list-style:none;background:var(--paper);border:1px solid var(--ink);border-top:0}
|
|
49
|
+
.option{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:10px 12px;cursor:pointer;font:14px/1.4 var(--mono);color:var(--soft)}
|
|
50
|
+
.option+.option{border-top:1px solid var(--line)}
|
|
51
|
+
.option.active{background:var(--alt);color:var(--ink)}
|
|
52
|
+
.option[aria-selected="true"]{color:var(--ink);font-weight:600}
|
|
53
|
+
.option[aria-selected="true"]::before{content:"";width:6px;height:6px;background:var(--ink);flex:none;align-self:center}
|
|
54
|
+
.option[aria-disabled="true"]{color:var(--muted);cursor:not-allowed;opacity:.6}
|
|
55
|
+
.option-note{margin-left:auto;font-size:11px;font-weight:400;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
|
|
56
|
+
.buttons{display:flex;flex-wrap:wrap;gap:12px}
|
|
57
|
+
button{padding:14px 22px;border:1px solid var(--ink);cursor:pointer}
|
|
58
|
+
.primary{background:var(--ink);color:var(--paper)}
|
|
59
|
+
.secondary{background:transparent;color:var(--ink)}
|
|
60
|
+
button:disabled{cursor:not-allowed;opacity:.4}
|
|
61
|
+
button:focus-visible,.picker-button:focus-visible,summary:focus-visible{outline:2px solid var(--red);outline-offset:2px}
|
|
62
|
+
.status{min-height:1.5em;margin:16px 0 0;color:var(--soft)}
|
|
63
|
+
.status.wait{color:var(--muted)}
|
|
64
|
+
.status.ok{color:var(--ink);border-left:3px solid var(--ink);padding-left:12px}
|
|
65
|
+
.status.error{color:var(--red);border-left:3px solid var(--red);padding-left:12px}
|
|
66
|
+
.details{margin-top:32px;border-top:1px solid var(--line);padding-top:12px}
|
|
67
|
+
.details summary{cursor:pointer;color:var(--muted);padding:6px 0}
|
|
68
|
+
.details pre{margin-top:10px}
|
|
69
|
+
[hidden]{display:none!important}
|
|
70
|
+
@media (max-width:480px){.row{grid-template-columns:1fr}.card{padding-top:28px}button{flex:1 1 100%}.picker{flex-basis:100%}}
|
|
71
|
+
`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CommandSession, RequestContext } from './command-context.js';
|
|
2
2
|
/**
|
|
3
|
-
* `status`, first step: a purchase already accepted on chain
|
|
4
|
-
*
|
|
3
|
+
* `status`, first step: a purchase already accepted on chain. Returns nothing when the saved
|
|
4
|
+
* record has to be checked; a missing backup secret then reports the pending setup answer.
|
|
5
5
|
*/
|
|
6
6
|
declare function statusOnChain(ctx: RequestContext): Promise<Record<string, unknown> | undefined>;
|
|
7
7
|
/** `status` from the saved purchase record: accepted, or still pending with a retry delay. */
|
|
@@ -16,11 +16,11 @@ import { resourceAllowed } from './resources.js';
|
|
|
16
16
|
import { retryGate } from './retry-gate.js';
|
|
17
17
|
import { readBackupSecret } from './wallets.js';
|
|
18
18
|
/**
|
|
19
|
-
* `status`, first step: a purchase already accepted on chain
|
|
20
|
-
*
|
|
19
|
+
* `status`, first step: a purchase already accepted on chain. Returns nothing when the saved
|
|
20
|
+
* record has to be checked; a missing backup secret then reports the pending setup answer.
|
|
21
21
|
*/
|
|
22
22
|
async function statusOnChain(ctx) {
|
|
23
|
-
const { c, d, env, o, paymentDetails
|
|
23
|
+
const { c, d, env, o, paymentDetails } = ctx;
|
|
24
24
|
if (!o.target || !isId(o.target))
|
|
25
25
|
throw usage();
|
|
26
26
|
if (d.agent)
|
|
@@ -35,8 +35,6 @@ async function statusOnChain(ctx) {
|
|
|
35
35
|
next: 'open',
|
|
36
36
|
...(await paymentDetails(async () => encryptedPurchaseStore(fileStore(join(c.stateDir, 'records')), await readBackupSecret(o, env)).load(purchaseKey(o.target)))),
|
|
37
37
|
};
|
|
38
|
-
if (!o.backupSecretFile && !env.ANNON_BACKUP_SECRET_FILE && !env.ANNON_BACKUP_SECRET)
|
|
39
|
-
return pendingSetup(o.target);
|
|
40
38
|
}
|
|
41
39
|
/** `status` from the saved purchase record: accepted, or still pending with a retry delay. */
|
|
42
40
|
async function status(ctx) {
|
|
@@ -66,14 +64,20 @@ async function status(ctx) {
|
|
|
66
64
|
}
|
|
67
65
|
if (d.agent && !accepted)
|
|
68
66
|
await retryGate({ ...c, purchaseId: o.target }).defer('settlement_pending', 30);
|
|
67
|
+
// A NEAR send that may be on chain: pending is not proof that it failed.
|
|
68
|
+
const uncertain = !accepted && record.nearSubmission?.state === 'uncertain';
|
|
69
69
|
return {
|
|
70
70
|
purchaseId: o.target,
|
|
71
71
|
state: accepted ? 'accepted' : 'pending',
|
|
72
|
+
...(uncertain ? { submission: 'uncertain' } : {}),
|
|
73
|
+
...(uncertain && record.nearSubmission?.txHash ? { txHash: record.nearSubmission.txHash } : {}),
|
|
72
74
|
...(await paymentDetails(async () => record)),
|
|
73
75
|
...(accepted ? {} : { retryAfterSeconds: 30 }),
|
|
74
76
|
next: accepted
|
|
75
77
|
? 'open'
|
|
76
|
-
:
|
|
78
|
+
: uncertain
|
|
79
|
+
? 'The wallet may have sent this payment. Do not pay again. Retry status until it is accepted or the purchase expires.'
|
|
80
|
+
: 'Check the payment and retry status. 1Click SUCCESS alone is not acceptance.',
|
|
77
81
|
};
|
|
78
82
|
}
|
|
79
83
|
/** `recover`: purchases found for the recovery wallet, with per-purchase public errors. */
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Input } from './cli.js';
|
|
2
|
+
import type { Environment } from './wallets.js';
|
|
3
|
+
/** Directory entries only: this cannot see unrelated custom directories or offline backups. */
|
|
4
|
+
export declare function assertNoSavedState(o: Partial<Input>, env: Environment): Promise<void>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Refuses a new backup secret where saved state already depends on an older one. */
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { configPath } from './agent.js';
|
|
5
|
+
import { defaultStateDir } from './config.js';
|
|
6
|
+
import { AdapterError } from './errors.js';
|
|
7
|
+
const savedState = () => new AdapterError('backup_secret_required', 'Saved records or backups already exist in the state directory or in ~/.local/share/annon; a new secret needs both empty. Restore their matching backup secret or select its existing file, because a new secret cannot decrypt them. To recover with a new secret instead, first move the whole old state, including ~/.local/share/annon, to a separate private archive and keep it.', 2);
|
|
8
|
+
const uncheckable = () => new AdapterError('backup_secret_required', 'Annon could not check the state directory for saved records or backups (unreadable config or state). Fix it before creating a backup secret.', 2);
|
|
9
|
+
async function hasEntries(dir) {
|
|
10
|
+
try {
|
|
11
|
+
return (await readdir(dir)).length > 0;
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (error.code === 'ENOENT')
|
|
15
|
+
return false;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Human state precedence (flag, environment, config, default), read locally without the
|
|
21
|
+
* deployment checks of loadConfig. The legacy default is checked too when it differs.
|
|
22
|
+
*/
|
|
23
|
+
async function stateDirs(o, env) {
|
|
24
|
+
let file = {};
|
|
25
|
+
try {
|
|
26
|
+
file = JSON.parse(await readFile(configPath(o, env), 'utf8'));
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code !== 'ENOENT')
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
const stateDir = o.stateDir ?? env.ANNON_STATE_DIR ?? file.stateDir ?? defaultStateDir();
|
|
33
|
+
if (typeof stateDir !== 'string' || !stateDir)
|
|
34
|
+
throw new Error('invalid state directory');
|
|
35
|
+
return new Set([resolve(stateDir), resolve(defaultStateDir())]);
|
|
36
|
+
}
|
|
37
|
+
/** Directory entries only: this cannot see unrelated custom directories or offline backups. */
|
|
38
|
+
export async function assertNoSavedState(o, env) {
|
|
39
|
+
let found = false;
|
|
40
|
+
try {
|
|
41
|
+
for (const dir of await stateDirs(o, env))
|
|
42
|
+
for (const name of ['records', 'backups'])
|
|
43
|
+
found ||= await hasEntries(join(dir, name));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw uncheckable();
|
|
47
|
+
}
|
|
48
|
+
if (found)
|
|
49
|
+
throw savedState();
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const version: string;
|
package/dist/node/wallets.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ import type { SplitCall } from '../pay/split.js';
|
|
|
3
3
|
import { type IdentityWallet } from '../wallet/wallet.js';
|
|
4
4
|
import type { Input } from './cli.js';
|
|
5
5
|
import { type Keychain } from './keychain.js';
|
|
6
|
+
export { readBackupSecret } from './backup-secret.js';
|
|
6
7
|
export type Environment = Record<string, string | undefined>;
|
|
7
8
|
export type LocalWallet = IdentityWallet & {
|
|
8
9
|
signUpload?: (intent: UploadIntent) => Promise<UploadAuth>;
|
|
9
10
|
sendBase?: (call: SplitCall, rpc: string) => Promise<void>;
|
|
10
11
|
};
|
|
11
12
|
export declare function identity(value: string): import("../settle-near/identity.js").Identity;
|
|
12
|
-
export declare function readBackupSecret(o: Partial<Input>, env: Environment): Promise<Uint8Array>;
|
|
13
13
|
export declare function loadWallet(o: Partial<Input>, env: Environment, nearRpc: string, keychain?: Keychain): Promise<LocalWallet>;
|
package/dist/node/wallets.js
CHANGED
|
@@ -7,11 +7,12 @@ import { privateKeyToAccount } from 'viem/accounts';
|
|
|
7
7
|
import { base } from 'viem/chains';
|
|
8
8
|
import { signNearUpload } from '../browser/wallets.js';
|
|
9
9
|
import { nep413Hash } from '../near/nep413.js';
|
|
10
|
-
import {
|
|
10
|
+
import { hex } from '../settle-near/codec.js';
|
|
11
11
|
import { identityJson, parseIdentity } from '../settle-near/identity.js';
|
|
12
12
|
import { nearKeyPairWallet } from '../wallet/wallet.js';
|
|
13
13
|
import { AdapterError } from './errors.js';
|
|
14
14
|
import { decodeKeychain, osKeychain } from './keychain.js';
|
|
15
|
+
export { readBackupSecret } from './backup-secret.js';
|
|
15
16
|
const credentialsError = () => new AdapterError('credentials_required', 'Select a wallet and provide its key through the documented environment variable or a named key file. Check the recovery identity matches.', 2);
|
|
16
17
|
export function identity(value) {
|
|
17
18
|
const at = value.indexOf(':');
|
|
@@ -24,15 +25,6 @@ async function source(file, env) {
|
|
|
24
25
|
return env.trim();
|
|
25
26
|
throw credentialsError();
|
|
26
27
|
}
|
|
27
|
-
export async function readBackupSecret(o, env) {
|
|
28
|
-
try {
|
|
29
|
-
const text = await source(o.backupSecretFile ?? env.ANNON_BACKUP_SECRET_FILE, env.ANNON_BACKUP_SECRET);
|
|
30
|
-
return bytes(text.startsWith('0x') ? text : `0x${text}`, 32);
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
throw new AdapterError('backup_secret_required', 'Provide a 32-byte random backup secret as 64 hex characters in ANNON_BACKUP_SECRET or a named backup secret file.', 2);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
28
|
export async function loadWallet(o, env, nearRpc, keychain = osKeychain) {
|
|
37
29
|
try {
|
|
38
30
|
const selected = o.wallet ?? (o.recovery ? identity(o.recovery).kind : env.ANNON_WALLET);
|
package/dist/pay/purchase.d.ts
CHANGED
|
@@ -76,9 +76,10 @@ export interface PurchaseRecord extends Versioned {
|
|
|
76
76
|
recoveryProved: boolean;
|
|
77
77
|
/** Source details are the reviewed 1Click quote. */
|
|
78
78
|
payment: PaymentSummary;
|
|
79
|
+
/** A NEAR send that may be on chain; `txHash` only when the wallet returned one. */
|
|
79
80
|
nearSubmission?: {
|
|
80
81
|
state: 'uncertain';
|
|
81
|
-
txHash
|
|
82
|
+
txHash?: string;
|
|
82
83
|
};
|
|
83
84
|
paymentState?: 'expired' | 'canceled';
|
|
84
85
|
purchase: Purchase;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { AnnonError } from '../errors.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Public evidence to retain whenever a submitted NEAR transaction cannot be confirmed. `txHash` is
|
|
4
|
+
* absent when a wallet may have broadcast but never returned a hash: unknown, never invented.
|
|
5
|
+
*/
|
|
3
6
|
export declare class TransactionUncertain extends AnnonError {
|
|
4
|
-
readonly txHash: string;
|
|
7
|
+
readonly txHash: string | undefined;
|
|
5
8
|
readonly purchaseId?: string | undefined;
|
|
6
9
|
readonly code = "transaction_uncertain";
|
|
7
|
-
constructor(txHash: string, purchaseId?: string | undefined);
|
|
10
|
+
constructor(txHash: string | undefined, purchaseId?: string | undefined);
|
|
8
11
|
}
|
package/dist/wallet/uncertain.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { AnnonError } from '../errors.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Public evidence to retain whenever a submitted NEAR transaction cannot be confirmed. `txHash` is
|
|
4
|
+
* absent when a wallet may have broadcast but never returned a hash: unknown, never invented.
|
|
5
|
+
*/
|
|
3
6
|
export class TransactionUncertain extends AnnonError {
|
|
4
7
|
txHash;
|
|
5
8
|
purchaseId;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defdone/annon",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
4
|
"description": "Files only the buyer can open: encrypted on the client, the key released by NEAR once the payment is verified.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://annon.dev",
|