@wishknish/knishio-client-ts 1.2.0 → 1.3.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishknish/knishio-client-ts",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "productName": "Knish.IO TypeScript SDK Client",
5
5
  "description": "TypeScript implementation of the Knish.IO SDK to consume Knish.IO GraphQL APIs with enhanced type safety.",
6
6
  "license": "GPL-3.0-or-later",
@@ -81,7 +81,11 @@
81
81
  "vitest": "^4.1.11"
82
82
  },
83
83
  "resolutions": {
84
- "wonka": "6.3.6"
84
+ "wonka": "6.3.6",
85
+ "esbuild": "^0.28.1"
86
+ },
87
+ "overrides": {
88
+ "esbuild": "^0.28.1"
85
89
  },
86
90
  "browserslist": [
87
91
  "> 1%",
package/src/AuthToken.ts CHANGED
@@ -53,7 +53,9 @@ import Wallet from '@/core/Wallet'
53
53
  * so the two can never drift: a field either round-trips or neither side compiles.
54
54
  *
55
55
  * `wallet.mlKemParameterSet` is optional because snapshots persisted before ML-KEM-1024 became
56
- * the default do not carry it — see {@link AuthToken.resolveMlKemParameterSet}.
56
+ * the default do not carry it — see {@link AuthToken.resolveMlKemParameterSet}. `wallet.token` is
57
+ * optional because snapshots persisted before re-logins were signed from the USER ContinuID
58
+ * wallet do not carry it; every such session was signed from an AUTH wallet.
57
59
  */
58
60
  export type AuthTokenSnapshot = {
59
61
  token: string
@@ -61,6 +63,7 @@ export type AuthTokenSnapshot = {
61
63
  pubkey: string
62
64
  encrypt: boolean
63
65
  wallet?: {
66
+ token?: string
64
67
  position: string | null
65
68
  characters: string | null
66
69
  mlKemParameterSet?: 1024 | 768
@@ -134,7 +137,7 @@ export default class AuthToken {
134
137
  static restore(snapshot: AuthTokenSnapshot, secret: string): AuthToken {
135
138
  const wallet = new Wallet({
136
139
  secret,
137
- token: 'AUTH',
140
+ token: snapshot.wallet?.token ?? 'AUTH',
138
141
  position: snapshot.wallet?.position ?? null,
139
142
  characters: snapshot.wallet?.characters ?? null,
140
143
  mlKemParameterSet: AuthToken.resolveMlKemParameterSet(snapshot)
@@ -232,6 +235,7 @@ export default class AuthToken {
232
235
  encrypt: this.$__encrypt,
233
236
  ...(this.$__wallet ? {
234
237
  wallet: {
238
+ token: this.$__wallet.token,
235
239
  position: this.$__wallet.position,
236
240
  characters: this.$__wallet.characters,
237
241
  mlKemParameterSet: this.$__wallet.mlKemParameterSet
@@ -1014,38 +1014,41 @@ export default class KnishIOClient {
1014
1014
  this.setCellSlug(cellSlug)
1015
1015
  }
1016
1016
 
1017
- // Auth in process...
1017
+ // Auth in process... Cleared in `finally`: a rejected login must not leave the guard set, or
1018
+ // executeQuery never refreshes an expired token again.
1018
1019
  this.$__authInProcess = true
1019
1020
 
1020
- // Auth token response
1021
- let response: Response
1022
-
1023
- // Authorized user (profile auth with secret)
1024
- if (secret) {
1025
- response = await this.requestProfileAuthToken({
1026
- secret,
1027
- encrypt
1028
- })
1029
- } else {
1030
- // Guest auth (no secret)
1031
- response = await this.requestGuestAuthToken({
1032
- cellSlug: cellSlug || this.getCellSlug(),
1033
- encrypt
1034
- })
1035
- }
1036
-
1037
- // Log success
1038
- if (this.$__authToken) {
1039
- this.log('info', `KnishIOClient::requestAuthToken() - Successfully retrieved auth token...`)
1040
- }
1021
+ try {
1022
+ // Auth token response
1023
+ let response: Response
1024
+
1025
+ // Authorized user (profile auth with secret)
1026
+ if (secret) {
1027
+ response = await this.requestProfileAuthToken({
1028
+ secret,
1029
+ encrypt
1030
+ })
1031
+ } else {
1032
+ // Guest auth (no secret)
1033
+ response = await this.requestGuestAuthToken({
1034
+ cellSlug: cellSlug || this.getCellSlug(),
1035
+ encrypt
1036
+ })
1037
+ }
1041
1038
 
1042
- // Handle encryption if needed
1043
- this.switchEncryption(encrypt)
1039
+ // Log success
1040
+ if (this.$__authToken) {
1041
+ this.log('info', `KnishIOClient::requestAuthToken() - Successfully retrieved auth token...`)
1042
+ }
1044
1043
 
1045
- // Auth process is stopped
1046
- this.$__authInProcess = false
1044
+ // Handle encryption if needed
1045
+ this.switchEncryption(encrypt)
1047
1046
 
1048
- return response
1047
+ return response
1048
+ } finally {
1049
+ // Auth process is stopped
1050
+ this.$__authInProcess = false
1051
+ }
1049
1052
  }
1050
1053
 
1051
1054
  /**
@@ -1233,17 +1236,20 @@ export default class KnishIOClient {
1233
1236
 
1234
1237
  /**
1235
1238
  * Query next ContinuId wallet
1239
+ *
1240
+ * @param token - Restricts the lookup to wallets of this token. Without it the validator falls
1241
+ * back to the bundle's newest wallet of ANY token when the bundle has no ContinuID pointer.
1236
1242
  */
1237
1243
  async queryContinuId({
1238
- bundle
1244
+ bundle,
1245
+ token = null
1239
1246
  }: {
1240
1247
  bundle: BundleHash | string
1248
+ token?: string | null
1241
1249
  }): Promise<Response> {
1242
1250
  const query = this.createQuery(QueryContinuId)
1243
1251
 
1244
- return this.executeQuery(query, {
1245
- bundle
1246
- }) as Promise<Response>
1252
+ return this.executeQuery(query, token ? { bundle, token } : { bundle }) as Promise<Response>
1247
1253
  }
1248
1254
 
1249
1255
  /**
@@ -2433,13 +2439,11 @@ export default class KnishIOClient {
2433
2439
  async withdrawBufferToken({
2434
2440
  tokenSlug,
2435
2441
  amount,
2436
- sourceWallet = null,
2437
- signingWallet = null
2442
+ sourceWallet = null
2438
2443
  }: {
2439
2444
  tokenSlug: TokenSlug | string
2440
2445
  amount: number | string
2441
2446
  sourceWallet?: Wallet | null
2442
- signingWallet?: Wallet | null
2443
2447
  }): Promise<Response> {
2444
2448
  this.log('info', `KnishIOClient::withdrawBufferToken() - Withdrawing ${amount} of ${tokenSlug} from buffer...`)
2445
2449
 
@@ -2454,12 +2458,7 @@ export default class KnishIOClient {
2454
2458
  // Initialize the withdraw buffer token mutation
2455
2459
  const recipients: Record<string, any> = {}
2456
2460
  recipients[this.getBundle()] = amount
2457
- // Only include signingWallet if it exists (exactOptionalPropertyTypes compliance)
2458
- const fillMoleculeParams: any = { recipients }
2459
- if (signingWallet) {
2460
- fillMoleculeParams.signingWallet = signingWallet
2461
- }
2462
- await mutation.fillMolecule(fillMoleculeParams)
2461
+ await mutation.fillMolecule({ recipients })
2463
2462
 
2464
2463
  // Execute the mutation
2465
2464
  const response = await this.executeQuery(mutation)
@@ -2487,29 +2486,43 @@ export default class KnishIOClient {
2487
2486
  } = {}): Promise<Response> {
2488
2487
  this.log('info', 'KnishIOClient::requestGuestAuthToken() - Requesting guest auth token...')
2489
2488
 
2490
- // Create the mutation (extends Mutation directly, not MutationProposeMolecule)
2491
- const mutation = this.createQuery(MutationRequestAuthorizationGuest)
2489
+ // Hold the auth guard so this login's own request does not start a nested token refresh.
2490
+ // Restore the caller's value: requestAuthToken holds it until its own `finally`.
2491
+ const authInProcess = this.$__authInProcess
2492
+ this.$__authInProcess = true
2493
+ try {
2494
+ // Create the mutation (extends Mutation directly, not MutationProposeMolecule)
2495
+ const mutation = this.createQuery(MutationRequestAuthorizationGuest)
2492
2496
 
2493
- // Execute with variables
2494
- const response = await this.executeQuery(mutation, {
2495
- cellSlug: cellSlug || this.getCellSlug()
2496
- })
2497
+ // Execute with variables
2498
+ const response = await this.executeQuery(mutation, {
2499
+ cellSlug: cellSlug || this.getCellSlug()
2500
+ })
2497
2501
 
2498
- if (!response) {
2499
- throw new CodeException('Guest auth token request failed')
2500
- }
2502
+ if (!response) {
2503
+ throw new CodeException('Guest auth token request failed')
2504
+ }
2501
2505
 
2502
- // Handle encryption if needed
2503
- if (encrypt) {
2504
- this.switchEncryption(true)
2505
- }
2506
+ // Handle encryption if needed
2507
+ if (encrypt) {
2508
+ this.switchEncryption(true)
2509
+ }
2506
2510
 
2507
- return response
2511
+ return response
2512
+ } finally {
2513
+ this.$__authInProcess = authInProcess
2514
+ }
2508
2515
  }
2509
2516
 
2510
2517
  /**
2511
2518
  * Request profile authentication token
2512
- * Matches JavaScript SDK requestProfileAuthToken implementation
2519
+ *
2520
+ * A returning user (the bundle has a USER ContinuID pointer) signs from the USER wallet
2521
+ * registered at that pointer, so validator 0.5.0+ marks the token proven and the identity keeps
2522
+ * read/subscription access to permissioned and private cells. A first login — or a pointer this
2523
+ * secret does not own — signs from a fresh AUTH wallet as before. A rejected pointer-signed
2524
+ * login falls back to the AUTH login exactly once, so one call sends at most two
2525
+ * authorization molecules (testnet allows 3 auths/min/IP).
2513
2526
  */
2514
2527
  async requestProfileAuthToken({
2515
2528
  secret,
@@ -2523,16 +2536,70 @@ export default class KnishIOClient {
2523
2536
  // Set the secret
2524
2537
  this.setSecret(secret)
2525
2538
 
2526
- // Generate a signing wallet with AUTH token (matching JavaScript SDK)
2527
- const wallet = new Wallet({
2528
- secret,
2529
- token: 'AUTH',
2530
- mlKemParameterSet: this.getMlKemParameterSet()
2531
- })
2539
+ // Hold the auth guard so this login's own requests do not start a nested token refresh (a
2540
+ // second login that would sign at the same pointer). Restore the caller's value:
2541
+ // requestAuthToken holds it until its own `finally`.
2542
+ const authInProcess = this.$__authInProcess
2543
+ this.$__authInProcess = true
2544
+ try {
2545
+ // Filtered to USER: unfiltered, a bundle without a pointer answers with its newest wallet of
2546
+ // any token, which is not an identity wallet the validator would accept as the signer.
2547
+ const pointer = (await this.queryContinuId({
2548
+ bundle: this.getBundle(),
2549
+ token: 'USER'
2550
+ }))?.payload() as Wallet | null
2551
+
2552
+ let pointerWallet: Wallet | null = null
2553
+ if (pointer && pointer.token === 'USER' && pointer.position) {
2554
+ const wallet = new Wallet({
2555
+ secret,
2556
+ token: 'USER',
2557
+ position: pointer.position,
2558
+ mlKemParameterSet: this.getMlKemParameterSet()
2559
+ })
2560
+ if (!pointer.address || wallet.address === pointer.address) {
2561
+ pointerWallet = wallet
2562
+ } else {
2563
+ this.log('warn', 'KnishIOClient::requestProfileAuthToken() - ContinuID wallet address does not derive from this secret; signing from a fresh AUTH wallet')
2564
+ }
2565
+ }
2566
+
2567
+ if (pointerWallet) {
2568
+ const response = await this.proposeProfileAuthorization(pointerWallet, encrypt)
2569
+ if (response.success()) {
2570
+ this.log('info', 'KnishIOClient::requestProfileAuthToken() - Auth token signed from the ContinuID pointer (proven)')
2571
+ return this.acceptProfileAuthorization(response, pointerWallet, encrypt)
2572
+ }
2573
+ this.log('warn', `KnishIOClient::requestProfileAuthToken() - Pointer-signed authorization rejected (${response.reason()}); falling back once to an AUTH-wallet login`)
2574
+ }
2575
+
2576
+ // Generate a signing wallet with AUTH token (matching JavaScript SDK)
2577
+ const wallet = new Wallet({
2578
+ secret,
2579
+ token: 'AUTH',
2580
+ mlKemParameterSet: this.getMlKemParameterSet()
2581
+ })
2582
+ const response = await this.proposeProfileAuthorization(wallet, encrypt)
2532
2583
 
2533
- // Create a molecule with the AUTH wallet as source
2584
+ if (!response.success()) {
2585
+ throw new AuthorizationRejectedException(
2586
+ `KnishIOClient::requestProfileAuthToken() - Authorization attempt rejected by ledger. Reason: ${response.reason()}`
2587
+ )
2588
+ }
2589
+
2590
+ this.log('info', `KnishIOClient::requestProfileAuthToken() - Auth token signed from a fresh AUTH wallet (${pointerWallet ? 'fallback after the pointer-signed login was rejected' : 'no usable ContinuID pointer'})`)
2591
+ return this.acceptProfileAuthorization(response, wallet, encrypt)
2592
+ } finally {
2593
+ this.$__authInProcess = authInProcess
2594
+ }
2595
+ }
2596
+
2597
+ /**
2598
+ * Signs and proposes a profile authorization molecule from the given source wallet.
2599
+ */
2600
+ private async proposeProfileAuthorization(wallet: Wallet, encrypt: boolean): Promise<ResponseRequestAuthorization> {
2534
2601
  const molecule = await this.createMolecule({
2535
- secret,
2602
+ secret: this.getSecret(),
2536
2603
  sourceWallet: wallet
2537
2604
  })
2538
2605
 
@@ -2543,7 +2610,7 @@ export default class KnishIOClient {
2543
2610
  })
2544
2611
 
2545
2612
  // Initialize the profile auth mutation.
2546
- // PQ-transport Phase E: convey the AUTH source wallet's ML-KEM public key as a SIGNED
2613
+ // PQ-transport Phase E: convey the source wallet's ML-KEM public key as a SIGNED
2547
2614
  // walletPubkey U-atom meta, so the validator can encrypt CipherHash responses back to THIS
2548
2615
  // wallet (the one that decrypts them). Signed → MITM can't swap the target. Conveyed
2549
2616
  // unconditionally (parity with JS/Kotlin/PHP), so even a plaintext auth primes enc_pubkey.
@@ -2562,21 +2629,26 @@ export default class KnishIOClient {
2562
2629
  throw new CodeException('Profile auth token request failed')
2563
2630
  }
2564
2631
 
2565
- // Did the authorization molecule get accepted?
2566
- if (response.success()) {
2567
- // Create & set an auth token from the response data
2568
- const authToken = AuthToken.create({
2569
- token: response.token(),
2570
- expiresAt: response.expiresAt(),
2571
- encrypt: String(response.encrypt()) === 'true',
2572
- pubkey: response.pubKey()
2573
- }, wallet)
2574
- this.setAuthToken(authToken)
2575
- } else {
2576
- throw new AuthorizationRejectedException(
2577
- `KnishIOClient::requestProfileAuthToken() - Authorization attempt rejected by ledger. Reason: ${response.reason()}`
2578
- )
2579
- }
2632
+ return response
2633
+ }
2634
+
2635
+ /**
2636
+ * Binds the accepted authorization's token to the wallet that signed it.
2637
+ */
2638
+ private acceptProfileAuthorization(
2639
+ response: ResponseRequestAuthorization,
2640
+ wallet: Wallet,
2641
+ encrypt: boolean
2642
+ ): ResponseRequestAuthorization {
2643
+ // Create & set an auth token from the response data
2644
+ const authToken = AuthToken.create({
2645
+ token: response.token(),
2646
+ expiresAt: response.expiresAt(),
2647
+ encrypt: String(response.encrypt()) === 'true',
2648
+ pubkey: response.pubKey()
2649
+ }, wallet)
2650
+ this.setAuthToken(authToken)
2651
+ this.lastMoleculeQuery = null
2580
2652
 
2581
2653
  // Handle encryption if needed
2582
2654
  if (encrypt) {
@@ -148,25 +148,6 @@ export default class AtomMeta {
148
148
  return this
149
149
  }
150
150
 
151
- /**
152
- * Set signing wallet metadata
153
- * @param signingWallet - Wallet used for signing
154
- * @return This instance for chaining
155
- */
156
- setSigningWallet(signingWallet: any): AtomMeta {
157
- this.merge({
158
- signingWallet: JSON.stringify({
159
- tokenSlug: signingWallet.token,
160
- bundleHash: signingWallet.bundle,
161
- address: signingWallet.address,
162
- position: signingWallet.position,
163
- pubkey: signingWallet.pubkey,
164
- characters: signingWallet.characters
165
- })
166
- })
167
- return this
168
- }
169
-
170
151
  /**
171
152
  * Add policy metadata
172
153
  * @param policy - Policy object to add
@@ -412,25 +412,8 @@ export default class Molecule {
412
412
  throw new SignatureMalformedException('No atoms available for signing!')
413
413
  }
414
414
 
415
- // Set signing position from the first atom
416
- let signingPosition = signingAtom.position
417
-
418
- // Get signing wallet from first atom's metas (matching JS SDK exactly)
419
- // This handles buffer withdraw reconciliation where a different wallet signs
420
- const signingWalletMeta = signingAtom.aggregatedMeta ? signingAtom.aggregatedMeta() : null
421
- const signingWalletJson = signingWalletMeta?.signingWallet as string | undefined
422
- if (signingWalletJson) {
423
- try {
424
- const parsedSigningWallet = JSON.parse(signingWalletJson)
425
- if (parsedSigningWallet.position) {
426
- signingPosition = parsedSigningWallet.position
427
- }
428
- } catch {
429
- // Invalid JSON in signingWallet meta, use default position
430
- }
431
- }
432
-
433
- // Signing position is required
415
+ // The private key comes from the first atom's own position
416
+ const signingPosition = signingAtom.position
434
417
  if (!signingPosition) {
435
418
  throw new SignatureMalformedException('Signing wallet must have a position!')
436
419
  }
@@ -938,7 +921,7 @@ export default class Molecule {
938
921
  metaType: string
939
922
  metaId: string
940
923
  rule: Rule[] | any[] | any // Accept single object or array
941
- policy?: Record<string, any>
924
+ policy?: Record<string, any> | null
942
925
  }): Molecule {
943
926
  if (!this.sourceWallet) {
944
927
  throw new Error('Source wallet required for createRule')
@@ -958,10 +941,8 @@ export default class Molecule {
958
941
  rule: JSON.stringify(rules)
959
942
  })
960
943
 
961
- // Add policies to meta object only if policy exists and has keys (matching JS SDK)
962
- if (policy && Object.keys(policy).length > 0) {
963
- atomMeta.addPolicy(policy)
964
- }
944
+ // JS createRule always adds the policy (its {} default still fills per-key defaults).
945
+ atomMeta.addPolicy(policy ?? {})
965
946
 
966
947
  // Create R-isotope atom for rule (matching JS SDK)
967
948
  this.addAtom(Atom.create({
@@ -1294,15 +1275,12 @@ export default class Molecule {
1294
1275
  * Matches JavaScript SDK Molecule.initWithdrawBuffer implementation exactly
1295
1276
  *
1296
1277
  * @param recipients - Map of recipientBundle → amount
1297
- * @param signingWallet - Optional signing wallet for reconciliation
1298
1278
  * @return This molecule instance for chaining
1299
1279
  */
1300
1280
  initWithdrawBuffer({
1301
- recipients,
1302
- signingWallet = null
1281
+ recipients
1303
1282
  }: {
1304
1283
  recipients: Record<string, number>
1305
- signingWallet?: any | null
1306
1284
  }): Molecule {
1307
1285
  if (!this.sourceWallet) {
1308
1286
  throw new Error('Source wallet required for buffer withdrawal')
@@ -1318,18 +1296,11 @@ export default class Molecule {
1318
1296
  throw new BalanceInsufficientException()
1319
1297
  }
1320
1298
 
1321
- // Set a meta signing position for molecule correct reconciliation
1322
- const firstAtomMeta = new AtomMeta()
1323
- if (signingWallet) {
1324
- firstAtomMeta.setSigningWallet(signingWallet)
1325
- }
1326
-
1327
1299
  // Debit full balance from source (B-isotope)
1328
1300
  this.addAtom(Atom.create({
1329
1301
  isotope: 'B',
1330
1302
  wallet: this.sourceWallet,
1331
1303
  value: -Number(this.sourceWallet.balance),
1332
- meta: firstAtomMeta,
1333
1304
  metaType: 'walletBundle',
1334
1305
  metaId: this.sourceWallet.bundle!
1335
1306
  }))
package/src/index.ts CHANGED
@@ -403,7 +403,7 @@ export {
403
403
  // MUST equal package.json's "version". The CI `version consistency` job enforces this via
404
404
  // .github/scripts/check-version.sh, which parses this exact line — keep the literal form
405
405
  // `export const SDK_VERSION = '<semver>'` intact so the gate can read it.
406
- export const SDK_VERSION = '1.2.0'
406
+ export const SDK_VERSION = '1.3.0'
407
407
  export const SDK_NAME = 'KnishIO-Client-TS'
408
408
  export const COMPATIBLE_SERVER_VERSIONS = [4, 5]
409
409
 
@@ -70,7 +70,6 @@ import Wallet from '@/core/Wallet'
70
70
  import Rule from '@/instance/rules/Rule'
71
71
  import { base64ToHex, chunkSubstr } from '@/libraries/strings'
72
72
  import { shake256 } from '@/libraries/crypto'
73
- import Dot from '@/libraries/Dot'
74
73
  import Molecule from '@/core/Molecule'
75
74
 
76
75
  // Type definitions for Molecule structure
@@ -214,12 +213,13 @@ export default class CheckMolecule {
214
213
  /**
215
214
  * Validate isotope U atoms
216
215
  * @return True if valid
217
- * @throws WrongTokenTypeException if token is not AUTH
216
+ * @throws WrongTokenTypeException if token is neither AUTH (fresh login) nor USER (re-login
217
+ * signed from the ContinuID pointer)
218
218
  * @throws AtomIndexException if index is not 0
219
219
  */
220
220
  isotopeU(): boolean {
221
221
  for (const atom of this.molecule.getIsotopes('U')) {
222
- if (atom.token !== 'AUTH') {
222
+ if (atom.token !== 'AUTH' && atom.token !== 'USER') {
223
223
  throw new WrongTokenTypeException(`Check::isotopeU() - "${atom.token}" is not a valid Token slug for "${atom.isotope}" isotope Atoms!`)
224
224
  }
225
225
 
@@ -747,22 +747,8 @@ export default class CheckMolecule {
747
747
  throw new SignatureMismatchException()
748
748
  }
749
749
 
750
- // Get a signing address
751
- let signingAddress = signingAtom.walletAddress
752
-
753
- // Get signing wallet from first atom's metas
754
- const signingWallet = Dot.get(signingAtom.aggregatedMeta(), 'signingWallet')
755
-
756
- // Try to get custom signing address from the metas (local molecule with server secret)
757
- if (signingWallet) {
758
- const parsedWallet = JSON.parse(signingWallet as string)
759
- const walletAddress = Dot.get(parsedWallet, 'address')
760
- if (walletAddress) {
761
- signingAddress = walletAddress as string
762
- }
763
- }
764
-
765
- if (address !== signingAddress) {
750
+ // The recovered address must match the address the signing atom claims
751
+ if (address !== signingAtom.walletAddress) {
766
752
  throw new SignatureMismatchException()
767
753
  }
768
754
 
@@ -47,7 +47,6 @@ License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
47
47
  */
48
48
 
49
49
  import MutationProposeMolecule from './MutationProposeMolecule'
50
- import type Wallet from '@/core/Wallet'
51
50
 
52
51
  /**
53
52
  * Mutation for withdrawing buffer tokens
@@ -57,15 +56,12 @@ export default class MutationWithdrawBufferToken extends MutationProposeMolecule
57
56
  * Fills the molecule with buffer withdrawal data
58
57
  */
59
58
  override fillMolecule({
60
- recipients,
61
- signingWallet
59
+ recipients
62
60
  }: {
63
61
  recipients: Record<string, any>
64
- signingWallet?: Wallet
65
62
  }): void {
66
- (this.$__molecule as any).initWithdrawBuffer({
67
- recipients,
68
- signingWallet
63
+ this.$__molecule.initWithdrawBuffer({
64
+ recipients
69
65
  })
70
66
  this.$__molecule.sign({})
71
67
  this.$__molecule.check(this.$__molecule.sourceWallet)
@@ -64,8 +64,8 @@ export default class QueryContinuId extends Query {
64
64
  constructor(graphQLClient: GraphQLClient, knishIOClient: KnishIOClient) {
65
65
  super(graphQLClient, knishIOClient)
66
66
 
67
- this.$__query = gql`query ($bundle: String!) {
68
- ContinuId(bundle: $bundle) {
67
+ this.$__query = gql`query ($bundle: String!, $token: String) {
68
+ ContinuId(bundle: $bundle, token: $token) {
69
69
  address,
70
70
  bundleHash,
71
71
  tokenSlug,
@@ -169,7 +169,8 @@ export default class ResponseProposeMolecule extends Response {
169
169
  code: 'HASH_MISMATCH'
170
170
  })
171
171
  }
172
- if (/ots.*position.*reuse|position.*already.*used|ots.*verification/.test(lc)) {
172
+ // Validator 0.5.0 words the same rejection as "OTS key reuse rejected ... is already consumed".
173
+ if (/ots.*(?:position|key).*reuse|position.*already.*(?:used|consumed)|ots.*verification/.test(lc)) {
173
174
  return new SignatureMismatchException(reason, {
174
175
  details: { reason },
175
176
  code: 'OTS_VERIFICATION_FAILED'
@@ -187,6 +188,22 @@ export default class ResponseProposeMolecule extends Response {
187
188
  code: 'VERIFICATION_FAILED'
188
189
  })
189
190
  }
191
+ // Validator 0.5.0 signer binding (Tier 1): atom 0's address is not the wallet registered at
192
+ // (bundle, token, position). Nothing was consumed.
193
+ if (/signer address mismatch/.test(lc)) {
194
+ return new SignatureMismatchException(reason, {
195
+ details: { reason },
196
+ code: 'SIGNER_ADDRESS_MISMATCH'
197
+ })
198
+ }
199
+ // Validator 0.5.0 (Tier 2): the signing key is consumed and the chain advanced, so the
200
+ // cached remainder is stale — handlePositionDrift keys on AtomIndexException.
201
+ if (/no accepted origin available/.test(lc)) {
202
+ return new AtomIndexException(reason, {
203
+ details: { reason },
204
+ code: 'ORIGIN_PENDING'
205
+ })
206
+ }
190
207
  return null
191
208
  }
192
209