@xgamesfun/solana 0.24.0 → 0.25.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.
Files changed (99) hide show
  1. package/DISCLAIMER.md +1 -1
  2. package/NOTICE +4 -4
  3. package/README.md +100 -52
  4. package/bin/xsolana.mjs +32 -11
  5. package/examples/blind-forge.json +2 -4
  6. package/examples/coin-flip-arena.json +2 -5
  7. package/examples/duel-arena.json +1 -4
  8. package/examples/fair-presale.json +1 -3
  9. package/examples/hero-trials.json +14 -2
  10. package/examples/ideas.json +788 -788
  11. package/examples/nft-raffle.json +1 -0
  12. package/examples/partner-vault.json +19 -3
  13. package/examples/sacrifice-raffle.json +2 -10
  14. package/examples/shelter-fair.json +1 -0
  15. package/examples/stakers-draw.json +4 -7
  16. package/examples/trade-in-shop.json +2 -3
  17. package/js/approve/details.mjs +117 -48
  18. package/js/approve/ed25519.mjs +5 -1
  19. package/js/approve/live-check.mjs +83 -26
  20. package/js/approve/page.html +217 -78
  21. package/js/approve/page.js +43 -15
  22. package/js/approve/server.mjs +276 -74
  23. package/js/chain/base58.js +7 -1
  24. package/js/chain/borsh.js +15 -11
  25. package/js/chain/bytes.js +11 -8
  26. package/js/chain/core.js +21 -5
  27. package/js/chain/curve.js +1 -1
  28. package/js/chain/lookup.js +118 -36
  29. package/js/chain/mint.js +6 -2
  30. package/js/chain/pda.js +2 -2
  31. package/js/chain/programs.js +6 -6
  32. package/js/chain/rpc.js +40 -14
  33. package/js/chain/token.js +13 -2
  34. package/js/chain/token2022.js +55 -16
  35. package/js/chain/tx.js +32 -22
  36. package/js/client/accounts.js +270 -80
  37. package/js/client/compressed.js +4 -3
  38. package/js/client/grant.js +165 -40
  39. package/js/client/idl/loot.js +1 -1
  40. package/js/client/idl/loot.json +104 -16
  41. package/js/client/ix.js +2121 -457
  42. package/js/client/pda.js +20 -18
  43. package/js/client/policy.js +12 -5
  44. package/js/client/random.js +2 -2
  45. package/js/client/sizes.js +220 -65
  46. package/js/client/traits.js +14 -5
  47. package/js/connect/connect.mjs +59 -17
  48. package/js/economy/check.mjs +3884 -690
  49. package/js/economy/cost.mjs +381 -57
  50. package/js/economy/examples.mjs +176 -35
  51. package/js/economy/explain.mjs +468 -110
  52. package/js/economy/guide.mjs +282 -59
  53. package/js/economy/legal.mjs +12 -6
  54. package/js/economy/live.mjs +62 -20
  55. package/js/economy/model.mjs +320 -97
  56. package/js/economy/plan.mjs +1022 -203
  57. package/js/economy/review.mjs +760 -0
  58. package/js/economy/schema.json +134 -372
  59. package/js/economy/secrets.mjs +17 -7
  60. package/js/economy/simulate.mjs +771 -178
  61. package/js/economy/trust.mjs +15 -0
  62. package/js/economy/txsize.mjs +15 -3
  63. package/js/economy/validate.mjs +61 -24
  64. package/js/index.js +1 -0
  65. package/js/mcp/server.mjs +43 -15
  66. package/js/mcp/tools.mjs +318 -80
  67. package/js/play/index.js +1960 -461
  68. package/js/play/keys.js +35 -9
  69. package/js/results/index.js +225 -32
  70. package/js/results/server.js +230 -0
  71. package/js/results/wire.js +64 -16
  72. package/package.json +6 -4
  73. package/release.json +5 -5
  74. package/skills/xsolana-economy/SKILL.md +52 -22
  75. package/skills/xsolana-economy/references/after-launch.md +32 -21
  76. package/skills/xsolana-economy/references/apps.md +47 -36
  77. package/skills/xsolana-economy/references/sentence-to-spec.md +149 -129
  78. package/skills/xsolana-economy/references/telling-the-person.md +15 -6
  79. package/skills/xsolana-economy/references/trust-and-cheating.md +126 -0
  80. package/types/chain/core.d.ts +1 -1
  81. package/types/chain/lookup.d.ts +1 -1
  82. package/types/client/accounts.d.ts +24 -3
  83. package/types/client/grant.d.ts +2 -2
  84. package/types/client/idl/loot.d.ts +22 -0
  85. package/types/client/ix.d.ts +54 -45
  86. package/types/client/pda.d.ts +3 -1
  87. package/types/economy/cost.d.mts +1 -1
  88. package/types/economy/explain.d.mts +3 -0
  89. package/types/economy/guide.d.mts +5 -0
  90. package/types/economy/model.d.mts +48 -3
  91. package/types/economy/plan.d.mts +44 -2
  92. package/types/economy/review.d.mts +45 -0
  93. package/types/economy/trust.d.mts +8 -0
  94. package/types/index.d.ts +1 -0
  95. package/types/mcp/tools.d.mts +62 -0
  96. package/types/play/index.d.ts +14 -25
  97. package/types/results/index.d.ts +8 -7
  98. package/types/results/server.d.ts +126 -0
  99. package/types/results/wire.d.ts +1 -1
@@ -1,86 +1,225 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="xsolana-token" content="__TOKEN__">
7
- <title>Approve going live · loot</title>
8
- <style>
9
- :root { --bg: #f4f5f7; --panel: #ffffff; --ink: #14171f; --muted: #5b6372; --line: #dfe2e8; --accent: #3446d6; --ok: #1d7f4a; --bad: #b42318; --warn: #8a5a00; --mono: ui-monospace, "SFMono-Regular", Menlo, monospace; }
10
- @media (prefers-color-scheme: dark) { :root { --bg: #0f1218; --panel: #171b23; --ink: #e8ebf2; --muted: #9aa3b5; --line: #2a303c; --accent: #8394ff; --ok: #4cc47f; --bad: #ff7a6b; --warn: #e8b04a; } }
11
- * { box-sizing: border-box; }
12
- body { margin: 0; background: var(--bg); color: var(--ink); font: 15px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif; padding: 32px max(16px, env(safe-area-inset-left)); }
13
- main { max-width: 720px; margin: 0 auto; display: flex; flex-direction: column; gap: 20px; }
14
- h1 { font-size: 1.5rem; margin: 0; }
15
- .muted { color: var(--muted); }
16
- .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 18px 20px; }
17
- .row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
18
- button { font: inherit; padding: 9px 16px; border-radius: 8px; border: 1px solid var(--accent); background: var(--accent); color: #fff; cursor: pointer; }
19
- button.ghost { background: transparent; color: var(--accent); }
20
- button:disabled { opacity: .5; cursor: default; }
21
- button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
22
- ol { margin: 0; padding-left: 22px; display: flex; flex-direction: column; gap: 6px; }
23
- li.done { color: var(--muted); text-decoration: line-through; text-decoration-color: var(--ok); }
24
- li .who { font-size: .8rem; color: var(--warn); margin-left: 6px; }
25
- li .details { margin: 4px 0 2px; padding-left: 18px; font-size: .85rem; overflow-wrap: anywhere; }
26
- code, .mono { font-family: var(--mono); font-size: .85em; overflow-wrap: anywhere; }
27
- #log { font-family: var(--mono); font-size: .82rem; white-space: pre-wrap; margin: 0; max-height: 220px; overflow: auto; }
28
- .ok { color: var(--ok); } .bad { color: var(--bad); }
29
- ul.warn { margin: 0; padding-left: 18px; color: var(--warn); font-size: .9rem; }
30
- .flush { margin: 0; } .note { margin: 10px 0 0; } .spread { justify-content: space-between; } .gap { margin-top: 12px; }
31
- #wallets { margin-top: 10px; }
32
- </style>
33
- </head>
34
- <body>
35
- <main>
36
- <header>
37
- <p class="muted mono flush">loot · devnet · runs on your computer only</p>
38
- <h1 id="title">Approve going live</h1>
39
- <p class="muted" id="sub">Loading…</p>
40
- </header>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="xsolana-token" content="__TOKEN__" />
7
+ <title>Approve going live · loot</title>
8
+ <style>
9
+ :root {
10
+ --bg: #f4f5f7;
11
+ --panel: #ffffff;
12
+ --ink: #14171f;
13
+ --muted: #5b6372;
14
+ --line: #dfe2e8;
15
+ --accent: #3446d6;
16
+ --ok: #1d7f4a;
17
+ --bad: #b42318;
18
+ --warn: #8a5a00;
19
+ --mono: ui-monospace, 'SFMono-Regular', Menlo, monospace;
20
+ }
21
+ @media (prefers-color-scheme: dark) {
22
+ :root {
23
+ --bg: #0f1218;
24
+ --panel: #171b23;
25
+ --ink: #e8ebf2;
26
+ --muted: #9aa3b5;
27
+ --line: #2a303c;
28
+ --accent: #8394ff;
29
+ --ok: #4cc47f;
30
+ --bad: #ff7a6b;
31
+ --warn: #e8b04a;
32
+ }
33
+ }
34
+ * {
35
+ box-sizing: border-box;
36
+ }
37
+ body {
38
+ margin: 0;
39
+ background: var(--bg);
40
+ color: var(--ink);
41
+ font:
42
+ 15px/1.55 system-ui,
43
+ -apple-system,
44
+ 'Segoe UI',
45
+ sans-serif;
46
+ padding: 32px max(16px, env(safe-area-inset-left));
47
+ }
48
+ main {
49
+ max-width: 720px;
50
+ margin: 0 auto;
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 20px;
54
+ }
55
+ h1 {
56
+ font-size: 1.5rem;
57
+ margin: 0;
58
+ }
59
+ .muted {
60
+ color: var(--muted);
61
+ }
62
+ .panel {
63
+ background: var(--panel);
64
+ border: 1px solid var(--line);
65
+ border-radius: 10px;
66
+ padding: 18px 20px;
67
+ }
68
+ .row {
69
+ display: flex;
70
+ flex-wrap: wrap;
71
+ gap: 10px;
72
+ align-items: center;
73
+ }
74
+ button {
75
+ font: inherit;
76
+ padding: 9px 16px;
77
+ border-radius: 8px;
78
+ border: 1px solid var(--accent);
79
+ background: var(--accent);
80
+ color: #fff;
81
+ cursor: pointer;
82
+ }
83
+ button.ghost {
84
+ background: transparent;
85
+ color: var(--accent);
86
+ }
87
+ button:disabled {
88
+ opacity: 0.5;
89
+ cursor: default;
90
+ }
91
+ button:focus-visible {
92
+ outline: 2px solid var(--accent);
93
+ outline-offset: 2px;
94
+ }
95
+ ol {
96
+ margin: 0;
97
+ padding-left: 22px;
98
+ display: flex;
99
+ flex-direction: column;
100
+ gap: 6px;
101
+ }
102
+ li.done {
103
+ color: var(--muted);
104
+ text-decoration: line-through;
105
+ text-decoration-color: var(--ok);
106
+ }
107
+ li .who {
108
+ font-size: 0.8rem;
109
+ color: var(--warn);
110
+ margin-left: 6px;
111
+ }
112
+ li .details {
113
+ margin: 4px 0 2px;
114
+ padding-left: 18px;
115
+ font-size: 0.85rem;
116
+ overflow-wrap: anywhere;
117
+ }
118
+ code,
119
+ .mono {
120
+ font-family: var(--mono);
121
+ font-size: 0.85em;
122
+ overflow-wrap: anywhere;
123
+ }
124
+ #log {
125
+ font-family: var(--mono);
126
+ font-size: 0.82rem;
127
+ white-space: pre-wrap;
128
+ margin: 0;
129
+ max-height: 220px;
130
+ overflow: auto;
131
+ }
132
+ .ok {
133
+ color: var(--ok);
134
+ }
135
+ .bad {
136
+ color: var(--bad);
137
+ }
138
+ ul.warn {
139
+ margin: 0;
140
+ padding-left: 18px;
141
+ color: var(--warn);
142
+ font-size: 0.9rem;
143
+ }
144
+ .flush {
145
+ margin: 0;
146
+ }
147
+ .note {
148
+ margin: 10px 0 0;
149
+ }
150
+ .spread {
151
+ justify-content: space-between;
152
+ }
153
+ .gap {
154
+ margin-top: 12px;
155
+ }
156
+ #wallets {
157
+ margin-top: 10px;
158
+ }
159
+ </style>
160
+ </head>
161
+ <body>
162
+ <main>
163
+ <header>
164
+ <p class="muted mono flush">loot · devnet · runs on your computer only</p>
165
+ <h1 id="title">Approve going live</h1>
166
+ <p class="muted" id="sub">Loading…</p>
167
+ </header>
41
168
 
42
- <section class="panel">
43
- <div class="row">
44
- <button id="connect">Connect a wallet</button>
45
- <span id="wallet" class="mono muted"></span>
46
- </div>
47
- <div class="row" id="wallets" hidden></div>
48
- <p class="muted note">Set the wallet to <b>devnet</b>. Each approval shows you the transactions before you sign; nothing leaves your wallet without it. Nobody else ever sees your key.</p>
49
- </section>
169
+ <section class="panel">
170
+ <div class="row">
171
+ <button id="connect">Connect a wallet</button>
172
+ <span id="wallet" class="mono muted"></span>
173
+ </div>
174
+ <div class="row" id="wallets" hidden></div>
175
+ <p class="muted note">
176
+ Set the wallet to <b>devnet</b>. Each approval shows you the transactions before you sign; nothing leaves your
177
+ wallet without it. Nobody else ever sees your key.
178
+ </p>
179
+ </section>
50
180
 
51
- <section class="panel">
52
- <div class="row spread">
53
- <b>What going live does</b>
54
- <span class="muted" id="cost"></span>
55
- </div>
56
- <ol id="steps" class="gap"></ol>
57
- </section>
181
+ <section class="panel">
182
+ <div class="row spread">
183
+ <b>What going live does</b>
184
+ <span class="muted" id="cost"></span>
185
+ </div>
186
+ <ol id="steps" class="gap"></ol>
187
+ </section>
58
188
 
59
- <section class="panel" id="legalbox">
60
- <b>Read this first</b>
61
- <ul class="gap" id="terms">
62
- <li>The loot program has not been independently audited. It and these tools come as is, with no warranty and no guarantee that they are safe: what is sent to the program can be lost. You use them at your own risk.</li>
63
- <li>Transactions can't be undone. You are responsible for every one you approve.</li>
64
- <li>Making sure your economy is legal everywhere it is offered is up to you. Nothing here is legal or financial advice.</li>
65
- </ul>
66
- <ul class="warn gap" id="legal" hidden></ul>
67
- <p class="muted note">The full text is in DISCLAIMER.md, in the package.</p>
68
- <label class="row gap"><input type="checkbox" id="legalok"> I have read this and accept it. Devnet is not real money; real money needs every point above answered first.</label>
69
- </section>
189
+ <section class="panel" id="legalbox">
190
+ <b>Read this first</b>
191
+ <ul class="gap" id="terms">
192
+ <li>
193
+ The loot program has not been independently audited. It and these tools come as is, with no warranty and no
194
+ guarantee that they are safe: what is sent to the program can be lost. You use them at your own risk.
195
+ </li>
196
+ <li>Transactions can't be undone. You are responsible for every one you approve.</li>
197
+ <li>
198
+ Making sure your economy is legal everywhere it is offered is up to you. Nothing here is legal or financial
199
+ advice.
200
+ </li>
201
+ </ul>
202
+ <ul class="warn gap" id="legal" hidden></ul>
203
+ <p class="muted note">The full text is in DISCLAIMER.md, in the package.</p>
204
+ <label class="row gap"
205
+ ><input type="checkbox" id="legalok" /> I have read this and accept it. Devnet is not real money; real money
206
+ needs every point above answered first.</label
207
+ >
208
+ </section>
70
209
 
71
- <section class="panel" id="warnbox" hidden>
72
- <b>Before you approve</b>
73
- <ul class="warn" id="warnings"></ul>
74
- </section>
210
+ <section class="panel" id="warnbox" hidden>
211
+ <b>Before you approve</b>
212
+ <ul class="warn" id="warnings"></ul>
213
+ </section>
75
214
 
76
- <section class="panel">
77
- <div class="row">
78
- <button id="approve" disabled>Approve the next batch</button>
79
- <span id="status" class="muted"></span>
80
- </div>
81
- <pre id="log"></pre>
82
- </section>
83
- </main>
84
- <script type="module" src="/js/approve/page.js"></script>
85
- </body>
215
+ <section class="panel">
216
+ <div class="row">
217
+ <button id="approve" disabled>Approve the next batch</button>
218
+ <span id="status" class="muted"></span>
219
+ </div>
220
+ <pre id="log"></pre>
221
+ </section>
222
+ </main>
223
+ <script type="module" src="/js/approve/page.js"></script>
224
+ </body>
86
225
  </html>
@@ -6,14 +6,14 @@
6
6
  import { fromBase64, toBase64 } from '/js/chain/bytes.js';
7
7
 
8
8
  const CHAIN = 'solana:devnet';
9
- const $ = (id) => document.getElementById(id);
9
+ const $ = id => document.getElementById(id);
10
10
 
11
11
  function walletRegistry() {
12
12
  const wallets = new Set();
13
13
  const api = Object.freeze({
14
14
  register: (...ws) => {
15
15
  for (const w of ws) if (w?.features) wallets.add(w);
16
- return () => ws.forEach((w) => wallets.delete(w));
16
+ return () => ws.forEach(w => wallets.delete(w));
17
17
  },
18
18
  });
19
19
  window.addEventListener('wallet-standard:register-wallet', ({ detail }) => {
@@ -24,7 +24,13 @@ function walletRegistry() {
24
24
  }
25
25
  });
26
26
  window.dispatchEvent(Object.assign(new Event('wallet-standard:app-ready'), { detail: api }));
27
- return () => [...wallets].filter((w) => (w.chains ?? []).some((c) => String(c).startsWith('solana:')) && w.features['standard:connect'] && w.features['solana:signTransaction']);
27
+ return () =>
28
+ [...wallets].filter(
29
+ w =>
30
+ (w.chains ?? []).some(c => String(c).startsWith('solana:')) &&
31
+ w.features['standard:connect'] &&
32
+ w.features['solana:signTransaction'],
33
+ );
28
34
  }
29
35
 
30
36
  export function approvalPage(token) {
@@ -36,7 +42,11 @@ export function approvalPage(token) {
36
42
  // While a batch is out for signing or sending, nothing re-enables Approve (a state refresh landing mid-way did).
37
43
  let busy = false;
38
44
  const api = async (path, body) => {
39
- const r = await fetch(path, { method: body ? 'POST' : 'GET', headers: { 'x-xsolana-token': token, 'content-type': 'application/json' }, body: body ? JSON.stringify(body) : undefined });
45
+ const r = await fetch(path, {
46
+ method: body ? 'POST' : 'GET',
47
+ headers: { 'x-xsolana-token': token, 'content-type': 'application/json' },
48
+ body: body ? JSON.stringify(body) : undefined,
49
+ });
40
50
  const data = await r.json().catch(() => ({}));
41
51
  if (!r.ok) throw new Error(data.error ?? `HTTP ${r.status}`);
42
52
  return data;
@@ -59,7 +69,7 @@ export function approvalPage(token) {
59
69
  $('cost').textContent = state.setupSol !== null && state.mode === 'new' ? `about ${state.setupSol} SOL` : '';
60
70
  const steps = $('steps');
61
71
  steps.replaceChildren(
62
- ...[...state.landed.map((l) => ({ ...l, done: true })), ...state.steps].map((s) => {
72
+ ...[...state.landed.map(l => ({ ...l, done: true })), ...state.steps].map(s => {
63
73
  const li = document.createElement('li');
64
74
  li.textContent = s.says;
65
75
  if (s.done) li.className = 'done';
@@ -73,7 +83,7 @@ export function approvalPage(token) {
73
83
  if (s.details?.length) {
74
84
  const ul = document.createElement('ul');
75
85
  ul.className = 'details';
76
- ul.append(...s.details.map((d) => Object.assign(document.createElement('li'), { textContent: d })));
86
+ ul.append(...s.details.map(d => Object.assign(document.createElement('li'), { textContent: d })));
77
87
  li.append(ul);
78
88
  }
79
89
  return li;
@@ -81,17 +91,27 @@ export function approvalPage(token) {
81
91
  );
82
92
  const warnings = state.warnings ?? [];
83
93
  $('warnbox').hidden = !warnings.length || state.done;
84
- $('warnings').replaceChildren(...warnings.map((w) => Object.assign(document.createElement('li'), { textContent: w })));
94
+ $('warnings').replaceChildren(
95
+ ...warnings.map(w => Object.assign(document.createElement('li'), { textContent: w })),
96
+ );
85
97
  // The notice (no warranty, at the owner's own risk, legal where it runs) and this economy's legal flags, in a
86
98
  // block of their own whatever the agent said: Approve waits until the owner ticks that they read them.
87
99
  const legal = state.legal ?? [];
88
100
  $('legalbox').hidden = Boolean(state.done);
89
101
  $('legal').hidden = !legal.length;
90
- $('legal').replaceChildren(...legal.map((f) => Object.assign(document.createElement('li'), { textContent: f.says })));
102
+ $('legal').replaceChildren(...legal.map(f => Object.assign(document.createElement('li'), { textContent: f.says })));
91
103
  const unread = !$('legalok').checked;
92
104
  // A live section the chain does not bear out: nothing can be approved.
93
105
  $('approve').disabled = state.done || !account || busy || Boolean(state.refused) || unread;
94
- $('status').textContent = state.done ? 'Done.' : state.refused ? 'Nothing can be approved: see above.' : !account ? 'Connect a wallet first.' : unread ? 'Read the notice above and tick that you have.' : `${state.remaining} transaction${state.remaining === 1 ? '' : 's'} to go.`;
106
+ $('status').textContent = state.done
107
+ ? 'Done.'
108
+ : state.refused
109
+ ? 'Nothing can be approved: see above.'
110
+ : !account
111
+ ? 'Connect a wallet first.'
112
+ : unread
113
+ ? 'Read the notice above and tick that you have.'
114
+ : `${state.remaining} transaction${state.remaining === 1 ? '' : 's'} to go.`;
95
115
  }
96
116
 
97
117
  async function refresh() {
@@ -147,7 +167,10 @@ export function approvalPage(token) {
147
167
  if (busy) return;
148
168
  const found = list();
149
169
  if (!found.length) {
150
- log('No Solana wallet in this browser. Install one (Phantom, Solflare, Backpack), set it to devnet, then reload.', 'bad');
170
+ log(
171
+ 'No Solana wallet in this browser. Install one (Phantom, Solflare, Backpack), set it to devnet, then reload.',
172
+ 'bad',
173
+ );
151
174
  return;
152
175
  }
153
176
  if (found.length === 1) {
@@ -157,8 +180,11 @@ export function approvalPage(token) {
157
180
  const box = $('wallets');
158
181
  box.replaceChildren(
159
182
  Object.assign(document.createElement('span'), { className: 'muted', textContent: 'Pick a wallet:' }),
160
- ...found.map((w) => {
161
- const button = Object.assign(document.createElement('button'), { className: 'ghost', textContent: w === wallet ? `${w.name} (connected)` : w.name });
183
+ ...found.map(w => {
184
+ const button = Object.assign(document.createElement('button'), {
185
+ className: 'ghost',
186
+ textContent: w === wallet ? `${w.name} (connected)` : w.name,
187
+ });
162
188
  button.addEventListener('click', () => connectTo(w));
163
189
  return button;
164
190
  }),
@@ -177,11 +203,13 @@ export function approvalPage(token) {
177
203
  try {
178
204
  const batch = await api('/api/prepare', { wallet: signer.address });
179
205
  const details = (batch.details ?? []).flat();
180
- log(`Asking your wallet to approve ${batch.transactions.length} transaction${batch.transactions.length === 1 ? '' : 's'} in the project ${batch.project}: ${batch.says.join(' | ')}${details.length ? `. ${details.join('. ')}` : ''}`);
206
+ log(
207
+ `Asking your wallet to approve ${batch.transactions.length} transaction${batch.transactions.length === 1 ? '' : 's'} in the project ${batch.project}: ${batch.says.join(' | ')}${details.length ? `. ${details.join('. ')}` : ''}`,
208
+ );
181
209
  const sign = signingWallet.features['solana:signTransaction'];
182
- const inputs = batch.transactions.map((t) => ({ account: signer, transaction: fromBase64(t), chain: CHAIN }));
210
+ const inputs = batch.transactions.map(t => ({ account: signer, transaction: fromBase64(t), chain: CHAIN }));
183
211
  const outputs = await sign.signTransaction(...inputs);
184
- const signed = outputs.map((o) => toBase64(o.signedTransaction));
212
+ const signed = outputs.map(o => toBase64(o.signedTransaction));
185
213
  $('status').textContent = 'Sending and confirming…';
186
214
  const state = await api('/api/complete', { wallet: signer.address, id: batch.id, transactions: signed });
187
215
  log(`Landed: ${state.signatures.slice(-batch.transactions.length).join(', ')}`, 'ok');