solana-studio 0.9.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e0ff18e6789fde7b36cbf2d6c29fcce763fca5c278e3b36dbf36bd8dd792df4
4
- data.tar.gz: 2e6867b16a35325e78c791e96ea909b93251428d1233dd9efb19fa938ab6106f
3
+ metadata.gz: a00c3f09eb98073668c9218045e49e705be6e803348ad9338e4295d32ca7c091
4
+ data.tar.gz: 16189d3a834e1880bbba0b37113823fdf95d4a690926d98f67120213818b70db
5
5
  SHA512:
6
- metadata.gz: 908f0395ddc2ee1f56133a5ec474a67d1494cd726dde4a15c6d4c0172edab971db6ba699894e298439a028cb7e7dd8a475127079ae90de41f27a8bac6a4c9838
7
- data.tar.gz: d7562cb9adac4442d8853d368b9c5cc59c85158f96103a9cf5ee6192657bf9da6cde560f5e541fe1addbc26bf45d8549370fc6790b7a4d20a0fcb5cefac4db29
6
+ metadata.gz: 43e0a031a5fb9c2bcb451c4bddcf2df0e79a1c1722c60e57e9fcd302699434736bf363dd12a6f1ee7f847f447a1b3aeb792e557a7874edc419714a66a9a8b094
7
+ data.tar.gz: 8613a4f8bcead4253fb9b1814b1fbb008d9d1f9c3db4e1e8be33b5ca5fc005ce2b7b4226f136454a52f4ef66ccec59c22cf968d6f78762558e51355a2e2de056
@@ -11,11 +11,22 @@
11
11
  //
12
12
  // walletOps.define('contest_entry', {
13
13
  // prepare: function (ctx) { ... return { transaction: <base58>, ...state }; },
14
- // complete: function (ctx, result, state) { ... }
14
+ // complete: function (ctx, result, state) { ... },
15
+ // signOnly: true
15
16
  // });
16
17
  //
17
18
  // walletOps.run('contest_entry', { contestId: 12 }, { provider: ... });
18
19
  //
20
+ // `signOnly` IS A REQUIREMENT OF THE TRANSACTION, NOT A PREFERENCE ABOUT THE
21
+ // WALLET, and it is the intent's to declare because only the intent knows the
22
+ // shape of the bytes it prepared. A CO-SIGNED transaction — one whose second
23
+ // signer slot is deliberately empty because a server fills it — cannot be
24
+ // broadcast by the wallet: the chain would reject it for a missing required
25
+ // signature, and, worse, the signed bytes the server needs would never come
26
+ // back to the app, so the flow fails with nothing to retry. Declaring it makes
27
+ // the transaction's own requirement outrank the wallet's capability. Omit it
28
+ // and nothing changes: a wallet that can broadcast still does.
29
+ //
19
30
  // THE ONE RULE A CALLER MUST FOLLOW: handlers are registered BY NAME at page
20
31
  // load, not passed as closures. A closure is precisely what cannot survive the
21
32
  // redirect — the page that held it no longer exists when the wallet answers. The
@@ -30,7 +41,8 @@
30
41
  // WHAT THIS FILE DOES NOT DO: it does not broadcast. Signing and sending are
31
42
  // different responsibilities with different failure modes, and the wallet that
32
43
  // broadcasts differs per vendor (Phantom deprecated its send-side deeplink, so
33
- // the app sends; Solflare and Backpack send for you). `complete` is told which
44
+ // the app sends; Solflare and Backpack send for you) and, where the intent
45
+ // declares `signOnly`, per TRANSACTION as well. `complete` is told which
34
46
  // happened and owns the RPC, exactly as the existing flows already do.
35
47
  (function (W) {
36
48
  'use strict';
@@ -107,6 +119,23 @@
107
119
  return Promise.resolve(handler.prepare(ctx)).then(function (prepared) {
108
120
  var intent = { op: name, ctx: ctx, state: prepared };
109
121
 
122
+ // THE DECLARATION TRAVELS IN THE JOURNAL, NOT LOOKED UP FROM THE HANDLER
123
+ // AT THE FAR END, and that is the whole reason this line exists here
124
+ // rather than inside signingHop. The signing hop is taken from TWO
125
+ // places: this one, where the handler is certainly registered, and the
126
+ // connect callback, which is a DIFFERENT PAGE that may not have loaded
127
+ // the script that defined this intent. A handler lookup there would come
128
+ // back empty and silently fall through to the send-side default — the
129
+ // exact branch a co-signed transaction must never take. A boolean on the
130
+ // intent is JSON-serialisable, which is the one thing this file requires
131
+ // of anything that has to survive a redirect.
132
+ //
133
+ // Set ONLY when true, so the journal an undeclared intent writes is
134
+ // byte-identical to the one it wrote before this option existed. That is
135
+ // also why JOURNAL_VERSION does not move: no reader's expectations
136
+ // change, and bumping it would strand every trip already in flight.
137
+ if (handler.signOnly) intent.signOnly = true;
138
+
110
139
  // Already connected? Go straight to signing. Otherwise connect first and
111
140
  // carry the intent through — sessions do not expire on any of the three
112
141
  // wallets, so this branch is taken once per user, not once per action.
@@ -145,18 +174,38 @@
145
174
  return signingHop(provider, connected, opts);
146
175
  }
147
176
 
148
- // Which signing method this wallet gets is a CAPABILITY QUESTION, not a
149
- // preference: Phantom's send-side deeplink is deprecated, so it signs and the
150
- // app broadcasts. Asking the provider keeps that fact in the profile table
151
- // where it is asserted, rather than branching on a wallet name here.
177
+ // Which signing method this wallet gets is TWO questions asked in order, and
178
+ // the order is the point.
179
+ //
180
+ // FIRST, what does the TRANSACTION allow? A co-signed transaction has an
181
+ // empty signer slot the server fills, so the wallet must sign and hand the
182
+ // bytes back — broadcasting it is not a worse option, it is a broken one.
183
+ // An intent that knows this declares `signOnly` and that answer is final.
184
+ //
185
+ // SECOND, and only for everything else, what can the WALLET do? Phantom's
186
+ // send-side deeplink is deprecated, so it signs and the app broadcasts;
187
+ // Solflare and Backpack broadcast for you. Asking the provider keeps that
188
+ // fact in the profile table where it is asserted, rather than branching on a
189
+ // wallet name here.
190
+ //
191
+ // Asking them the other way round is the bug this replaced: capability alone
192
+ // sent every co-signed transaction to the wallet's broadcaster on any wallet
193
+ // that had one.
194
+ //
195
+ // A wallet with no signTransaction at all would be refused BY NAME by the
196
+ // provider's own capability gate rather than quietly broadcast here. No
197
+ // profile is in that state today (all three sign), and this is the branch
198
+ // that would have to be revisited if one ever were.
152
199
  function signingHop(provider, journal, opts) {
200
+ var intent = journal.intent;
153
201
  var payload = {
154
202
  journal: journal,
155
- transaction: journal.intent.state.transaction,
203
+ transaction: intent.state.transaction,
156
204
  redirectLink: opts.redirectLink,
157
- intent: journal.intent
205
+ intent: intent
158
206
  };
159
- return provider.can('signAndSendTransaction')
207
+ var signOnly = !!(intent && intent.signOnly);
208
+ return (!signOnly && provider.can('signAndSendTransaction'))
160
209
  ? provider.beginSignAndSendTransaction(payload)
161
210
  : provider.beginSignTransaction(payload);
162
211
  }
@@ -234,6 +283,18 @@
234
283
  if (!handler || typeof handler.prepare !== 'function' || typeof handler.complete !== 'function') {
235
284
  throw new Error('walletOps.define("' + name + '") needs both prepare and complete');
236
285
  }
286
+ // REFUSED RATHER THAN COERCED, because every wrong answer here fails in
287
+ // the dangerous direction. A typo'd `signOnly: 'true'` read as truthy
288
+ // would sign-only a flow that wanted the wallet to broadcast; read
289
+ // strictly, it silently sends a co-signed transaction to a broadcaster.
290
+ // Neither is discoverable at the call site, and both surface as a chain
291
+ // error one page death later. So the option is a boolean or it is a bug.
292
+ if (handler.signOnly !== undefined && typeof handler.signOnly !== 'boolean') {
293
+ throw new Error(
294
+ 'walletOps.define("' + name + '") signOnly must be true or false, got ' +
295
+ typeof handler.signOnly
296
+ );
297
+ }
237
298
  handlers[name] = handler;
238
299
  },
239
300
 
@@ -16,5 +16,5 @@ module SolanaStudio
16
16
  # through the normal cycle. Splitting the version out is the same shape
17
17
  # studio-engine already uses (lib/studio/version.rb) and hands each file back
18
18
  # to its real owner: this one to the release, the gemspec to the PR.
19
- VERSION = "0.9.0"
19
+ VERSION = "0.9.1"
20
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solana-studio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-08 00:00:00.000000000 Z
11
+ date: 2026-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ed25519