@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.
Files changed (87) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/CLI.md +173 -13
  3. package/README.md +47 -10
  4. package/dist/api/annon-types.d.ts +2 -1
  5. package/dist/api/annon.js +9 -2
  6. package/dist/browser/index.d.ts +1 -1
  7. package/dist/browser/login-bundle.js +61 -60
  8. package/dist/browser/login-dom.d.ts +13 -0
  9. package/dist/browser/login-dom.js +71 -0
  10. package/dist/browser/login-dropdown.d.ts +32 -0
  11. package/dist/browser/login-dropdown.js +129 -0
  12. package/dist/browser/login-format.d.ts +16 -0
  13. package/dist/browser/login-format.js +52 -0
  14. package/dist/browser/login-status.d.ts +70 -0
  15. package/dist/browser/login-status.js +85 -0
  16. package/dist/browser/login-steps.d.ts +6 -0
  17. package/dist/browser/login-steps.js +35 -0
  18. package/dist/browser/login-terms.d.ts +10 -0
  19. package/dist/browser/login-terms.js +69 -0
  20. package/dist/browser/login-view.d.ts +34 -0
  21. package/dist/browser/login-view.js +227 -0
  22. package/dist/browser/login-wallet.d.ts +28 -0
  23. package/dist/browser/login-wallet.js +179 -0
  24. package/dist/browser/login.js +154 -142
  25. package/dist/browser/purchase-bundle.js +29 -29
  26. package/dist/browser/purchases.d.ts +4 -0
  27. package/dist/browser/purchases.js +14 -0
  28. package/dist/browser/reconcile.d.ts +41 -0
  29. package/dist/browser/reconcile.js +77 -0
  30. package/dist/node/adapter-checks.d.ts +3 -1
  31. package/dist/node/adapter-checks.js +3 -1
  32. package/dist/node/adapter.js +10 -3
  33. package/dist/node/agent.js +2 -1
  34. package/dist/node/approval-status.d.ts +34 -0
  35. package/dist/node/approval-status.js +71 -0
  36. package/dist/node/backup-secret-init.d.ts +9 -0
  37. package/dist/node/backup-secret-init.js +103 -0
  38. package/dist/node/backup-secret.d.ts +35 -0
  39. package/dist/node/backup-secret.js +104 -0
  40. package/dist/node/browser-login.d.ts +2 -0
  41. package/dist/node/browser-login.js +95 -16
  42. package/dist/node/buy-commands.js +17 -9
  43. package/dist/node/buy-flow.d.ts +18 -0
  44. package/dist/node/buy-flow.js +89 -0
  45. package/dist/node/cli-flags.d.ts +10 -0
  46. package/dist/node/cli-flags.js +68 -0
  47. package/dist/node/cli.d.ts +9 -1
  48. package/dist/node/cli.js +87 -95
  49. package/dist/node/command-context.js +1 -1
  50. package/dist/node/config.d.ts +4 -0
  51. package/dist/node/config.js +6 -2
  52. package/dist/node/core-adapter.js +4 -0
  53. package/dist/node/login-page.d.ts +7 -0
  54. package/dist/node/login-page.js +77 -0
  55. package/dist/node/main.js +20 -2
  56. package/dist/node/output/discovery.d.ts +23 -0
  57. package/dist/node/output/discovery.js +52 -0
  58. package/dist/node/output/human.d.ts +10 -0
  59. package/dist/node/output/human.js +46 -0
  60. package/dist/node/output/purchase.d.ts +10 -0
  61. package/dist/node/output/purchase.js +78 -0
  62. package/dist/node/output/text.d.ts +10 -0
  63. package/dist/node/output/text.js +43 -0
  64. package/dist/node/purchase-commands.d.ts +2 -2
  65. package/dist/node/purchase-commands.js +14 -6
  66. package/dist/node/purchase-ref.d.ts +15 -0
  67. package/dist/node/purchase-ref.js +93 -0
  68. package/dist/node/saved-state.d.ts +4 -0
  69. package/dist/node/saved-state.js +50 -0
  70. package/dist/node/shell/complete.d.ts +2 -0
  71. package/dist/node/shell/complete.js +17 -0
  72. package/dist/node/shell/session.d.ts +20 -0
  73. package/dist/node/shell/session.js +219 -0
  74. package/dist/node/shell/welcome.d.ts +9 -0
  75. package/dist/node/shell/welcome.js +39 -0
  76. package/dist/node/shell/words.d.ts +1 -0
  77. package/dist/node/shell/words.js +36 -0
  78. package/dist/node/shops.d.ts +5 -0
  79. package/dist/node/shops.js +22 -0
  80. package/dist/node/version.d.ts +1 -0
  81. package/dist/node/version.js +3 -0
  82. package/dist/node/wallets.d.ts +1 -1
  83. package/dist/node/wallets.js +2 -10
  84. package/dist/pay/purchase.d.ts +2 -1
  85. package/dist/wallet/uncertain.d.ts +6 -3
  86. package/dist/wallet/uncertain.js +4 -1
  87. package/package.json +1 -1
@@ -0,0 +1,227 @@
1
+ import { BASE_USDC, NEAR_USDC } from '../settle-near/terms.js';
2
+ import { formatUnits, nonceHex, record, secondsTime, text } from './login-format.js';
3
+ import { describeTerms } from './login-terms.js';
4
+ /** The whole request as JSON, with a NEP-413 nonce as hex instead of 32 numbers. */
5
+ export function technicalDetails(q) {
6
+ const payload = record(q.payload);
7
+ return JSON.stringify(q.operation === 'near.nep413' && 'nonce' in payload
8
+ ? { ...q, payload: { ...payload, nonce: nonceHex(payload.nonce) } }
9
+ : q, null, 2);
10
+ }
11
+ const NETWORKS = {
12
+ 'near:mainnet': 'NEAR mainnet',
13
+ 'near:testnet': 'NEAR testnet',
14
+ 'near:sandbox': 'NEAR sandbox',
15
+ 'eip155:8453': 'Base',
16
+ };
17
+ export const networkName = (chain) => NETWORKS[chain] ? `${NETWORKS[chain]} (${chain})` : chain;
18
+ /** Signed texts the SDK and CLI produce, by their first line. */
19
+ const TEXTS = [
20
+ ['Annon: list a file for sale', 'Sign the listing terms'],
21
+ ['Annon: publish a file', 'Publish a file'],
22
+ ['Annon: reserve encrypted file hosting', 'Reserve file hosting'],
23
+ ['Annon: verify purchase recovery', 'Prove you can recover a purchase'],
24
+ ['Annon: open a purchased file again', 'Open a purchased file again'],
25
+ ['Annon: open a file again', 'Open a listed file again'],
26
+ ['Annon: stop selling a file', 'Stop selling a file'],
27
+ ['Annon: request sponsored file storage', 'Request sponsored file storage'],
28
+ ];
29
+ const LOGIN = 'Annon login: ';
30
+ const OFF_CHAIN = 'Your wallet signs exactly this text. Annon uses the signature only as your approval of what it says. Signing sends no transaction from your wallet.';
31
+ /** Characters that hide or reorder text: controls other than tab and newline, bidi overrides, zero-width marks. */
32
+ const HIDDEN = [
33
+ [0x00, 0x08],
34
+ [0x0b, 0x1f],
35
+ [0x7f, 0x9f],
36
+ [0x200b, 0x200f],
37
+ [0x202a, 0x202e],
38
+ [0x2066, 0x2069],
39
+ [0xfeff, 0xfeff],
40
+ ];
41
+ const hasHidden = (s) => [...s].some((c) => {
42
+ const n = c.codePointAt(0) ?? 0;
43
+ return HIDDEN.some(([from, to]) => n >= from && n <= to);
44
+ });
45
+ const hiddenWarning = (s) => hasHidden(s)
46
+ ? [
47
+ 'The text contains invisible or direction-changing characters. Check it in Technical details.',
48
+ ]
49
+ : [];
50
+ function signedText(q, kind, extra) {
51
+ const message = text(record(q.payload).message);
52
+ const login = message.startsWith(LOGIN);
53
+ const known = TEXTS.find(([prefix]) => message === prefix || message.startsWith(`${prefix}\n`));
54
+ const near = q.operation === 'near.nep413';
55
+ return {
56
+ title: login
57
+ ? `Sign in with your ${near ? 'NEAR' : 'EVM'} wallet`
58
+ : (known?.[1] ?? 'Sign a message'),
59
+ rows: [
60
+ { label: 'Action', value: kind },
61
+ { label: 'Network', value: networkName(q.chain) },
62
+ {
63
+ label: near ? 'Account' : 'Address',
64
+ value: q.account || 'Chosen in your wallet',
65
+ mono: !!q.account,
66
+ },
67
+ ...extra,
68
+ ],
69
+ message: { label: 'Text to sign', text: message },
70
+ note: login
71
+ ? `This proves you control the ${near ? 'account' : 'address'}. It adds no key and sends no transaction.`
72
+ : known?.[0] === 'Annon: verify purchase recovery'
73
+ ? 'This proves you control the recovery identity named in the text. It sends no transaction and moves no funds.'
74
+ : OFF_CHAIN,
75
+ terms: [],
76
+ warnings: hiddenWarning(message),
77
+ };
78
+ }
79
+ function typedData(q) {
80
+ const p = record(q.payload);
81
+ const domain = record(p.domain);
82
+ const m = record(p.message);
83
+ const contract = text(domain.verifyingContract);
84
+ const chain = `eip155:${text(domain.chainId)}`;
85
+ const usdc = p.primaryType === 'TransferWithAuthorization' &&
86
+ contract.toLowerCase() === BASE_USDC &&
87
+ chain === 'eip155:8453';
88
+ const warnings = chain === q.chain ? [] : [`The typed data names ${chain}, not ${q.chain}.`];
89
+ if (p.primaryType !== 'TransferWithAuthorization' || !usdc)
90
+ return {
91
+ title: 'Sign typed data',
92
+ rows: [
93
+ { label: 'Action', value: `Sign EIP-712 data (${text(p.primaryType)})` },
94
+ { label: 'Network', value: networkName(q.chain) },
95
+ { label: 'Address', value: q.account || 'Chosen in your wallet', mono: !!q.account },
96
+ { label: 'Contract', value: contract, mono: true },
97
+ ],
98
+ message: { label: 'Data to sign', text: JSON.stringify(m, null, 2) },
99
+ note: 'Your wallet signs this data. Signing sends no transaction from your wallet.',
100
+ terms: [],
101
+ warnings: [...warnings, 'This is not a USDC transfer the page recognises. Read the data.'],
102
+ };
103
+ const amount = `${formatUnits(m.value, 6)} USDC`;
104
+ return {
105
+ title: 'Approve a payment',
106
+ amount,
107
+ rows: [
108
+ { label: 'Action', value: 'Authorize a USDC transfer (EIP-3009)' },
109
+ { label: 'Network', value: networkName(q.chain) },
110
+ { label: 'Amount', value: amount },
111
+ { label: 'To', value: text(m.to), mono: true },
112
+ { label: 'From', value: text(m.from) || q.account, mono: true },
113
+ { label: 'Token', value: `USDC ${contract}`, mono: true },
114
+ { label: 'Valid until', value: secondsTime(m.validBefore) },
115
+ { label: 'Authorization nonce', value: text(m.nonce), mono: true },
116
+ ],
117
+ note: 'Signing sends no transaction and costs no gas. It authorizes this exact USDC transfer, which can be submitted on Base once, until the time shown. After that it is void.',
118
+ terms: [],
119
+ warnings: [
120
+ ...warnings,
121
+ ...(q.account && text(m.from).toLowerCase() !== q.account.toLowerCase()
122
+ ? [`The transfer is from ${text(m.from)}, not the requested ${q.account}.`]
123
+ : []),
124
+ ],
125
+ };
126
+ }
127
+ const gas = (v) => {
128
+ const raw = text(v);
129
+ return /^[0-9]+$/.test(raw) ? `${formatUnits(raw, 12)} Tgas` : raw;
130
+ };
131
+ const yocto = (v) => {
132
+ const raw = text(v);
133
+ return !/^[0-9]+$/.test(raw)
134
+ ? raw
135
+ : raw === '0'
136
+ ? 'None'
137
+ : raw.length < 19
138
+ ? `${raw} yoctoNEAR`
139
+ : `${formatUnits(raw, 24)} NEAR`;
140
+ };
141
+ function transaction(q) {
142
+ const p = record(q.payload);
143
+ const args = record(p.args);
144
+ const receiver = text(p.receiverId);
145
+ const from = { label: 'From account', value: q.account || 'Chosen in your wallet', mono: true };
146
+ const sends = 'Your wallet sends this transaction from your account and pays its gas in NEAR.';
147
+ if (p.method !== 'ft_transfer_call')
148
+ return {
149
+ title: 'Approve a NEAR transaction',
150
+ rows: [
151
+ { label: 'Action', value: `Call ${text(p.method)}` },
152
+ { label: 'Network', value: networkName(q.chain) },
153
+ from,
154
+ { label: 'Contract', value: receiver, mono: true },
155
+ { label: 'Deposit', value: yocto(p.deposit) },
156
+ { label: 'Gas limit', value: gas(p.gas) },
157
+ ],
158
+ message: { label: 'Arguments', text: JSON.stringify(p.args ?? {}, null, 2) },
159
+ note: `${sends} ${p.deposit === '0' ? 'It attaches no deposit.' : 'It attaches the deposit shown.'}`,
160
+ terms: [],
161
+ warnings: [],
162
+ };
163
+ let msg = {};
164
+ try {
165
+ msg = record(JSON.parse(text(args.msg)));
166
+ }
167
+ catch { }
168
+ const usdc = receiver === NEAR_USDC || (msg.route === 'near-usdc' && text(msg.asset) === receiver);
169
+ const amount = usdc
170
+ ? `${formatUnits(args.amount, 6)} USDC`
171
+ : `${text(args.amount)} base units of ${receiver}`;
172
+ return {
173
+ title: 'Approve a payment',
174
+ amount,
175
+ rows: [
176
+ { label: 'Action', value: 'Send a token transfer (ft_transfer_call)' },
177
+ { label: 'Network', value: networkName(q.chain) },
178
+ { label: 'Amount', value: amount },
179
+ { label: 'To', value: text(args.receiver_id), mono: true },
180
+ from,
181
+ { label: 'Token', value: `${usdc ? 'USDC ' : ''}${receiver}`, mono: true },
182
+ ...(msg.file_id ? [{ label: 'File', value: text(msg.file_id), mono: true }] : []),
183
+ ...(msg.expires_at
184
+ ? [{ label: 'Purchase valid until', value: secondsTime(msg.expires_at) }]
185
+ : []),
186
+ { label: 'Deposit', value: yocto(p.deposit) },
187
+ { label: 'Gas limit', value: gas(p.gas) },
188
+ ],
189
+ message: { label: 'Transfer message', text: text(args.msg) },
190
+ note: `${sends} It moves the amount above to ${text(args.receiver_id)} and attaches ${p.deposit === '1' ? '1 yoctoNEAR, which NEAR token transfers require' : `a deposit of ${yocto(p.deposit)}`}.`,
191
+ terms: [],
192
+ warnings: usdc
193
+ ? []
194
+ : ['The page does not recognise this token. The amount is in its base units.'],
195
+ };
196
+ }
197
+ /** The summary of one signing request. Unknown operations are described as such, never guessed. */
198
+ export function describeRequest(q) {
199
+ const s = q.operation === 'near.nep413'
200
+ ? signedText(q, 'Sign a message (NEP-413)', [
201
+ { label: 'Recipient', value: text(record(q.payload).recipient), mono: true },
202
+ ])
203
+ : q.operation === 'evm.personalSign'
204
+ ? signedText(q, 'Sign a message (personal_sign)', [])
205
+ : q.operation === 'evm.signTypedData'
206
+ ? typedData(q)
207
+ : q.operation === 'near.signTransaction'
208
+ ? transaction(q)
209
+ : {
210
+ title: 'Unsupported request',
211
+ rows: [{ label: 'Operation', value: text(q.operation) }],
212
+ note: 'The page cannot sign this request.',
213
+ terms: [],
214
+ warnings: ['Unknown operation. Do not approve it.'],
215
+ };
216
+ const terms = describeTerms(q.terms, s.amount ? signedPrice(q) : undefined);
217
+ return { ...s, terms: terms.rows, warnings: [...s.warnings, ...terms.warnings] };
218
+ }
219
+ /** The base-unit amount a payment request signs, for comparison with the CLI's terms. */
220
+ function signedPrice(q) {
221
+ const p = record(q.payload);
222
+ return q.operation === 'evm.signTypedData'
223
+ ? text(record(p.message).value)
224
+ : q.operation === 'near.signTransaction'
225
+ ? text(record(p.args).amount)
226
+ : undefined;
227
+ }
@@ -0,0 +1,28 @@
1
+ import type { SigningRequest, SigningResult } from '../node/signing.js';
2
+ export type Outcome = {
3
+ kind: 'result';
4
+ result: SigningResult;
5
+ }
6
+ /** A redirect wallet returns a NEP-413 result through /callback. */
7
+ | {
8
+ kind: 'redirect';
9
+ }
10
+ /** The account-only sign-in for a transaction finished; the next click sends it. */
11
+ | {
12
+ kind: 'connected';
13
+ account: string;
14
+ };
15
+ export declare function pageWallets(callbacks: {
16
+ /** Remember which request a redirect wallet's opaque `state` belongs to. */
17
+ expect(state: string, id: string): void;
18
+ forget(state: string): void;
19
+ /**
20
+ * Called just before the wallet's send for request `id`: marks it sent for good and resolves
21
+ * only once the CLI acknowledged it. A rejection means the wallet is never called.
22
+ */
23
+ sending(id: string): Promise<void>;
24
+ }): {
25
+ /** True when the next click on this request only connects the wallet. */
26
+ needsConnect(q: SigningRequest, id: string): boolean;
27
+ run(q: SigningRequest, id: string): Promise<Outcome>;
28
+ };
@@ -0,0 +1,179 @@
1
+ /**
2
+ * The wallet side of the approval page: one user click runs at most one wallet window, so a
3
+ * popup is never opened after an earlier popup has used up the click's user activation.
4
+ *
5
+ * - NEP-413 messages: `signMessage` directly, with the account chosen in the wallet. No sign-in.
6
+ * - NEAR transactions: one click connects (account-only sign-in), the next click sends.
7
+ * - EVM: the injected provider, which needs no popup from the page.
8
+ */
9
+ import { Buffer } from 'buffer';
10
+ import { signInAccountOnly } from '../wallet/account-only.js';
11
+ import { evmWallet } from '../wallet/wallet.js';
12
+ import { Refusal, SendUncertain } from './login-status.js';
13
+ /** Signing-request chains the page can bind a NEAR wallet to. Anything else is refused. */
14
+ const NEAR_NETWORKS = new Map([
15
+ ['near:mainnet', 'mainnet'],
16
+ ['near:testnet', 'testnet'],
17
+ ]);
18
+ /** The selector's only RPC per network. The page CSP allows exactly these (browser-login.ts). */
19
+ const NEAR_RPC = {
20
+ mainnet: 'https://free.rpc.fastnear.com',
21
+ testnet: 'https://test.rpc.fastnear.com',
22
+ };
23
+ /**
24
+ * NEAR wallets offered on this page and the networks each is bound to. MyNearWallet is retired
25
+ * and no longer connects to apps. HOT's selector module sends no network with its requests, so
26
+ * it is offered for mainnet only and never loaded for another network.
27
+ */
28
+ const NEAR_WALLETS = new Map([
29
+ ['meteor-wallet', { name: 'Meteor Wallet', networks: ['mainnet', 'testnet'] }],
30
+ ['hot-wallet', { name: 'HOT Wallet', networks: ['mainnet'] }],
31
+ ]);
32
+ /** The NEAR network and wallet a request may use, or a refusal before any wallet code loads. */
33
+ function nearTarget(q, id) {
34
+ const networkId = NEAR_NETWORKS.get(q.chain);
35
+ if (!networkId)
36
+ throw new Refusal(`Browser NEAR wallets support near:mainnet and near:testnet, not ${q.chain}.`);
37
+ const offered = NEAR_WALLETS.get(id);
38
+ if (!offered)
39
+ throw new Refusal('Unknown NEAR wallet');
40
+ if (!offered.networks.includes(networkId))
41
+ throw new Refusal(`${offered.name} is offered for near:mainnet only. Pick Meteor Wallet.`);
42
+ return { networkId, key: `${id}:${networkId}` };
43
+ }
44
+ async function evmSign(q) {
45
+ const provider = window.ethereum;
46
+ if (!provider)
47
+ throw new Refusal('Install an injected EVM wallet.');
48
+ const accounts = (await provider.request({ method: 'eth_requestAccounts' }));
49
+ const address = q.account || accounts[0];
50
+ if (!address)
51
+ throw new Refusal('No account');
52
+ const chain = await provider.request({ method: 'eth_chainId' });
53
+ if (`eip155:${BigInt(String(chain))}` !== q.chain)
54
+ throw new Refusal(`Select ${q.chain} in your wallet.`);
55
+ const wallet = evmWallet(provider, address.toLowerCase());
56
+ return q.operation === 'evm.personalSign'
57
+ ? {
58
+ signature: (await wallet.authorize(q.payload.message))?.sig ?? '',
59
+ account: wallet.identity.id,
60
+ }
61
+ : { signature: await wallet.signTypedData(q.payload) };
62
+ }
63
+ export function pageWallets(callbacks) {
64
+ /** Wallets this page has loaded and signed out of any older connection, per wallet and network. */
65
+ const loaded = new Map();
66
+ /** Accounts from this page's own account-only sign-in, per wallet and network. */
67
+ const connected = new Map();
68
+ async function load(id, networkId, key) {
69
+ const cached = loaded.get(key);
70
+ if (cached)
71
+ return cached;
72
+ // Only the chosen wallet is loaded, on first use: the page starts without wallet code
73
+ // touching storage or listeners, and a refused wallet never runs.
74
+ const [{ setupWalletSelector }, module] = await Promise.all([
75
+ import('@near-wallet-selector/core'),
76
+ id === 'hot-wallet'
77
+ ? import('@near-wallet-selector/hot-wallet').then((m) => m.setupHotWallet())
78
+ : import('@near-wallet-selector/meteor-wallet').then((m) => m.setupMeteorWallet()),
79
+ ]);
80
+ const selector = await setupWalletSelector({
81
+ network: networkId,
82
+ fallbackRpcUrls: [NEAR_RPC[networkId]],
83
+ modules: [module],
84
+ });
85
+ const wallet = await selector.wallet(id);
86
+ // A reused loopback origin may hold an older connection. Never let it sign silently.
87
+ await wallet.signOut();
88
+ loaded.set(key, wallet);
89
+ return wallet;
90
+ }
91
+ async function nearSign(q, id) {
92
+ const { networkId, key } = nearTarget(q, id);
93
+ const wallet = await load(id, networkId, key);
94
+ if (q.operation === 'near.nep413') {
95
+ if (!wallet.signMessage)
96
+ throw new Refusal('This wallet cannot sign NEP-413 messages.');
97
+ const p = q.payload;
98
+ const state = crypto.randomUUID();
99
+ callbacks.expect(state, q.id);
100
+ let signed;
101
+ try {
102
+ signed = await wallet.signMessage({ ...p, nonce: Buffer.from(p.nonce), state });
103
+ }
104
+ catch (error) {
105
+ callbacks.forget(state);
106
+ throw error;
107
+ }
108
+ if (!signed)
109
+ return { kind: 'redirect' }; // /callback forwards the fragment
110
+ callbacks.forget(state);
111
+ if (q.account && signed.accountId !== q.account)
112
+ throw new Refusal(`The wallet signed as ${signed.accountId}. Sign with ${q.account}.`);
113
+ return { kind: 'result', result: signed };
114
+ }
115
+ let accounts = connected.get(key);
116
+ if (!accounts) {
117
+ accounts = await signInAccountOnly(wallet);
118
+ const account = q.account || accounts[0]?.accountId;
119
+ if (!account || !accounts.some((a) => a.accountId === account))
120
+ throw new Refusal('Select the requested NEAR account.');
121
+ connected.set(key, accounts);
122
+ return { kind: 'connected', account };
123
+ }
124
+ const account = q.account || accounts[0]?.accountId;
125
+ if (!account || !accounts.some((a) => a.accountId === account)) {
126
+ connected.delete(key);
127
+ throw new Refusal('Select the requested NEAR account.');
128
+ }
129
+ const p = q.payload;
130
+ const actions = [
131
+ {
132
+ // Selector v10 passes Borsh Action structs through to its transaction encoder.
133
+ functionCall: {
134
+ methodName: p.method,
135
+ args: Buffer.from(JSON.stringify(p.args)),
136
+ gas: BigInt(p.gas),
137
+ deposit: BigInt(p.deposit),
138
+ },
139
+ },
140
+ ];
141
+ // From here on the wallet may broadcast: any failure is uncertain and this request is never
142
+ // tried again from the page. The CLI must know before the wallet is called.
143
+ try {
144
+ await callbacks.sending(q.id);
145
+ }
146
+ catch {
147
+ throw new Refusal('This page did not call the wallet, because the CLI did not accept the send. Another page may already have sent it: check the terminal before paying.');
148
+ }
149
+ let hash;
150
+ try {
151
+ const outcome = await wallet.signAndSendTransaction({
152
+ signerId: account,
153
+ receiverId: p.receiverId,
154
+ actions,
155
+ });
156
+ hash = outcome?.transaction?.hash;
157
+ }
158
+ catch (error) {
159
+ throw new SendUncertain('wallet send failed', { cause: error });
160
+ }
161
+ if (typeof hash !== 'string' || !hash)
162
+ throw new SendUncertain('no transaction hash');
163
+ return { kind: 'result', result: { txHash: hash } };
164
+ }
165
+ return {
166
+ /** True when the next click on this request only connects the wallet. */
167
+ needsConnect(q, id) {
168
+ const networkId = NEAR_NETWORKS.get(q.chain);
169
+ return (q.operation === 'near.signTransaction' &&
170
+ networkId !== undefined &&
171
+ !connected.has(`${id}:${networkId}`));
172
+ },
173
+ async run(q, id) {
174
+ if (q.operation.startsWith('evm.'))
175
+ return { kind: 'result', result: await evmSign(q) };
176
+ return nearSign(q, id);
177
+ },
178
+ };
179
+ }