@wishknish/knishio-client-ts 0.8.2 → 0.9.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/dist/index.cjs +425 -263
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -17
- package/dist/index.d.ts +44 -17
- package/dist/index.iife.js +425 -265
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +447 -285
- package/dist/index.js.map +1 -1
- package/package.json +6 -9
- package/src/KnishIOClient.ts +180 -57
- package/src/constants.ts +1 -2
- package/src/core/Molecule.ts +53 -0
- package/src/core/Wallet.ts +84 -3
- package/src/libraries/Decimal.ts +1 -1
- package/src/libraries/GraphQLClient.ts +83 -0
- package/src/libraries/Hex.ts +4 -3
- package/src/libraries/UrqlClientWrapper.ts +13 -13
- package/src/libraries/crypto.ts +4 -4
- package/src/mutation/MutationClaimShadowWallet.ts +3 -1
- package/src/mutation/MutationTransferTokens.ts +18 -0
- package/src/query/QueryActiveSession.ts +0 -1
- package/src/query/QueryPolicy.ts +0 -1
- package/src/response/ResponseAuthorizationGuest.ts +2 -2
- package/src/response/ResponseBalance.ts +9 -6
- package/src/response/ResponseLinkIdentifier.ts +1 -1
- package/src/response/ResponseRequestAuthorizationGuest.ts +2 -2
- package/src/schemas/index.ts +2 -1
- package/src/subscribe/Subscribe.ts +3 -2
- package/src/types/assertions.ts +1 -6
- package/src/types/client.ts +12 -19
- package/src/types/guards.ts +2 -3
- package/src/types/index.ts +6 -0
- package/src/validation/UNIVERSAL_CONFIGURATION_INTERFACES.ts +3 -5
- package/src/validation/ValidationService.ts +6 -15
- package/src/validation/schemas.ts +5 -1
- package/src/versions/HashAtom.ts +11 -9
- package/src/query/QueryUserActivity.ts +0 -153
- package/src/response/ResponseQueryUserActivity.ts +0 -89
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wishknish/knishio-client-ts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|
|
@@ -71,17 +71,12 @@
|
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@types/isomorphic-fetch": "^0.0.39",
|
|
74
|
-
"@types/jest": "^30.0.0",
|
|
75
74
|
"@types/jssha": "^3.0.0",
|
|
76
75
|
"@types/node": "^22.8.1",
|
|
77
|
-
"@types/pg": "^8.10.0",
|
|
78
76
|
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
|
79
77
|
"@typescript-eslint/parser": "^8.15.0",
|
|
80
78
|
"eslint": "^9.16.0",
|
|
81
|
-
"jest": "^30.2.0",
|
|
82
|
-
"pg": "^8.11.0",
|
|
83
79
|
"prettier": "^3.4.2",
|
|
84
|
-
"ts-jest": "^29.4.6",
|
|
85
80
|
"tsup": "^8.3.5",
|
|
86
81
|
"tsx": "^4.20.5",
|
|
87
82
|
"typescript": "^5.6.3",
|
|
@@ -100,10 +95,12 @@
|
|
|
100
95
|
"build:test": "tsup src/index.ts --format esm,cjs,iife --tsconfig tsconfig.build.json --clean",
|
|
101
96
|
"build:dev": "tsup src/index.ts --format esm,cjs --dts --sourcemap --watch",
|
|
102
97
|
"typecheck": "tsc --noEmit",
|
|
103
|
-
"lint": "eslint src
|
|
98
|
+
"lint": "eslint src",
|
|
99
|
+
"lint:fix": "eslint src --fix",
|
|
104
100
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
105
|
-
"test": "vitest",
|
|
106
|
-
"test:
|
|
101
|
+
"test": "vitest run",
|
|
102
|
+
"test:watch": "vitest",
|
|
103
|
+
"test:coverage": "vitest run --coverage",
|
|
107
104
|
"test:ui": "vitest --ui",
|
|
108
105
|
"dev": "vite",
|
|
109
106
|
"prepublishOnly": "npm run build",
|
package/src/KnishIOClient.ts
CHANGED
|
@@ -50,7 +50,8 @@ import Decimal from '@/libraries/Decimal'
|
|
|
50
50
|
import GraphQLClient from '@/libraries/GraphQLClient'
|
|
51
51
|
import {
|
|
52
52
|
generateBundleHash,
|
|
53
|
-
generateSecret
|
|
53
|
+
generateSecret,
|
|
54
|
+
generateBatchId
|
|
54
55
|
} from '@/libraries/crypto'
|
|
55
56
|
import Molecule from '@/core/Molecule'
|
|
56
57
|
import Wallet from '@/core/Wallet'
|
|
@@ -80,7 +81,6 @@ import QueryBatchHistory from '@/query/QueryBatchHistory'
|
|
|
80
81
|
import QueryAtom from '@/query/QueryAtom'
|
|
81
82
|
import QueryPolicy from '@/query/QueryPolicy'
|
|
82
83
|
import QueryActiveSession from '@/query/QueryActiveSession'
|
|
83
|
-
import QueryUserActivity from '@/query/QueryUserActivity'
|
|
84
84
|
import QueryToken from '@/query/QueryToken'
|
|
85
85
|
import QueryMetaTypeViaAtom from '@/query/QueryMetaTypeViaAtom'
|
|
86
86
|
import QueryMetaTypeViaMolecule from '@/query/QueryMetaTypeViaMolecule'
|
|
@@ -121,6 +121,7 @@ import {
|
|
|
121
121
|
TransferBalanceException,
|
|
122
122
|
WalletShadowException,
|
|
123
123
|
StackableUnitAmountException,
|
|
124
|
+
StackableUnitDecimalsException,
|
|
124
125
|
AuthorizationRejectedException,
|
|
125
126
|
MolecularHashMismatchException,
|
|
126
127
|
SignatureMismatchException,
|
|
@@ -136,7 +137,9 @@ import type {
|
|
|
136
137
|
MetaType,
|
|
137
138
|
MetaId,
|
|
138
139
|
BatchId,
|
|
139
|
-
MetaFilter
|
|
140
|
+
MetaFilter,
|
|
141
|
+
RequestPolicy,
|
|
142
|
+
TransferRecipientInput
|
|
140
143
|
} from '@/types'
|
|
141
144
|
|
|
142
145
|
/**
|
|
@@ -152,6 +155,10 @@ export default class KnishIOClient {
|
|
|
152
155
|
private $__client!: GraphQLClient
|
|
153
156
|
private $__serverSdkVersion: number = 3
|
|
154
157
|
private $__logging: boolean = false
|
|
158
|
+
// Default urql request policy applied to reads when the caller doesn't pass
|
|
159
|
+
// one. null = urql's default (cache-first). Server/sync clients set this to
|
|
160
|
+
// 'network-only' so a long-lived client never serves a stale cached read.
|
|
161
|
+
private $__defaultRequestPolicy: RequestPolicy | null = null
|
|
155
162
|
private $__authTokenObjects: Record<string, AuthToken | null> = {}
|
|
156
163
|
private $__authToken: AuthToken | null = null
|
|
157
164
|
private $__authInProcess: boolean = false
|
|
@@ -176,6 +183,7 @@ export default class KnishIOClient {
|
|
|
176
183
|
socket?: { socketUri: string | null; appKey?: string } | null
|
|
177
184
|
serverSdkVersion?: number
|
|
178
185
|
logging?: boolean
|
|
186
|
+
defaultRequestPolicy?: RequestPolicy | null
|
|
179
187
|
}) {
|
|
180
188
|
// Phase 2 Enhancement: Use standardized configuration validation
|
|
181
189
|
const standardValidationResult = ConfigValidator.validateClientConfig(config)
|
|
@@ -213,14 +221,19 @@ export default class KnishIOClient {
|
|
|
213
221
|
serverSdkVersion = 3,
|
|
214
222
|
logging = false
|
|
215
223
|
} = validatedConfig
|
|
216
|
-
|
|
224
|
+
// Read from the RAW config: defaultRequestPolicy is an optional add-on the
|
|
225
|
+
// strict schema now permits, but the validated/inferred shape may not carry
|
|
226
|
+
// it depending on the parser, so take it straight from the caller's config.
|
|
227
|
+
const defaultRequestPolicy = (config.defaultRequestPolicy ?? null) as RequestPolicy | null
|
|
228
|
+
|
|
217
229
|
this.initialize({
|
|
218
230
|
uri: uri as string | string[],
|
|
219
231
|
cellSlug: cellSlug as string | null,
|
|
220
232
|
socket: socket as { socketUri: string | null; appKey?: string } | null,
|
|
221
233
|
client: client as GraphQLClient | null,
|
|
222
234
|
serverSdkVersion,
|
|
223
|
-
logging
|
|
235
|
+
logging,
|
|
236
|
+
defaultRequestPolicy
|
|
224
237
|
})
|
|
225
238
|
}
|
|
226
239
|
|
|
@@ -233,7 +246,8 @@ export default class KnishIOClient {
|
|
|
233
246
|
socket = null,
|
|
234
247
|
client = null,
|
|
235
248
|
serverSdkVersion = 3,
|
|
236
|
-
logging = false
|
|
249
|
+
logging = false,
|
|
250
|
+
defaultRequestPolicy = null
|
|
237
251
|
}: {
|
|
238
252
|
uri: string | string[]
|
|
239
253
|
cellSlug?: string | null
|
|
@@ -241,6 +255,7 @@ export default class KnishIOClient {
|
|
|
241
255
|
client?: GraphQLClient | null
|
|
242
256
|
serverSdkVersion?: number
|
|
243
257
|
logging?: boolean
|
|
258
|
+
defaultRequestPolicy?: RequestPolicy | null
|
|
244
259
|
}): void {
|
|
245
260
|
this.reset()
|
|
246
261
|
|
|
@@ -269,6 +284,9 @@ export default class KnishIOClient {
|
|
|
269
284
|
})
|
|
270
285
|
|
|
271
286
|
this.$__serverSdkVersion = serverSdkVersion
|
|
287
|
+
// Config (not session state) — set here, NOT cleared in reset(), mirroring
|
|
288
|
+
// serverSdkVersion. A re-initialize updates it.
|
|
289
|
+
this.$__defaultRequestPolicy = defaultRequestPolicy
|
|
272
290
|
}
|
|
273
291
|
|
|
274
292
|
/**
|
|
@@ -324,6 +342,22 @@ export default class KnishIOClient {
|
|
|
324
342
|
return this.$__serverSdkVersion
|
|
325
343
|
}
|
|
326
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Gets the client's default urql request policy (null = urql default).
|
|
347
|
+
*/
|
|
348
|
+
getDefaultRequestPolicy(): RequestPolicy | null {
|
|
349
|
+
return this.$__defaultRequestPolicy
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Sets the default urql request policy applied to reads that don't specify
|
|
354
|
+
* one. Pass 'network-only' on a long-lived server/sync client so it never
|
|
355
|
+
* serves a stale cached read; null restores urql's default (cache-first).
|
|
356
|
+
*/
|
|
357
|
+
setDefaultRequestPolicy(policy: RequestPolicy | null): void {
|
|
358
|
+
this.$__defaultRequestPolicy = policy
|
|
359
|
+
}
|
|
360
|
+
|
|
327
361
|
/**
|
|
328
362
|
* Reset common properties
|
|
329
363
|
*/
|
|
@@ -596,7 +630,16 @@ export default class KnishIOClient {
|
|
|
596
630
|
|
|
597
631
|
// Execute the query/mutation, forwarding any urql context (e.g.
|
|
598
632
|
// requestPolicy) assembled by the caller (queryMeta) / createQueryContext().
|
|
599
|
-
|
|
633
|
+
// When the caller did NOT specify a request policy, fall back to this
|
|
634
|
+
// client's defaultRequestPolicy (e.g. a long-lived server/sync client set
|
|
635
|
+
// to 'network-only' so it never serves a stale read from the urql cache —
|
|
636
|
+
// removing the footgun of having to pass network-only on every call).
|
|
637
|
+
// Precedence: per-call context.requestPolicy > client default > a query's
|
|
638
|
+
// own createQueryContext() (e.g. ContinuId) > urql default.
|
|
639
|
+
const effectiveContext = (this.$__defaultRequestPolicy && !(context as Record<string, any>).requestPolicy)
|
|
640
|
+
? { requestPolicy: this.$__defaultRequestPolicy, ...context }
|
|
641
|
+
: context
|
|
642
|
+
return await query.execute({ variables: variables || {}, context: effectiveContext })
|
|
600
643
|
}
|
|
601
644
|
|
|
602
645
|
/**
|
|
@@ -760,6 +803,96 @@ export default class KnishIOClient {
|
|
|
760
803
|
return await this.executeQuery(query)
|
|
761
804
|
}
|
|
762
805
|
|
|
806
|
+
/**
|
|
807
|
+
* Fund N recipients from a single source in ONE molecule (multi-recipient sibling of
|
|
808
|
+
* transferToken). Each recipient gets its own subset of stackable units (or a fungible
|
|
809
|
+
* amount); a remainder returns the rest to the sender. Conserves:
|
|
810
|
+
* -balance + Σamounts + (balance - Σ) === 0.
|
|
811
|
+
*
|
|
812
|
+
* @param token - token slug
|
|
813
|
+
* @param recipients - destinations (bundleHash + units OR amount + optional batchId)
|
|
814
|
+
* @param sourceWallet - optional pre-resolved source (defaults to the client's own wallet)
|
|
815
|
+
*/
|
|
816
|
+
async transferTokens({
|
|
817
|
+
token,
|
|
818
|
+
recipients,
|
|
819
|
+
sourceWallet = null
|
|
820
|
+
}: {
|
|
821
|
+
token: string
|
|
822
|
+
recipients: TransferRecipientInput[]
|
|
823
|
+
sourceWallet?: Wallet | null
|
|
824
|
+
}): Promise<Response | null> {
|
|
825
|
+
// Per-recipient amount: stackable -> unit count; fungible -> explicit amount (never both)
|
|
826
|
+
const amounts: number[] = recipients.map(recipient => {
|
|
827
|
+
const unitCount = recipient.units ? recipient.units.length : 0
|
|
828
|
+
if (unitCount > 0) {
|
|
829
|
+
if (recipient.amount !== null && recipient.amount !== undefined && recipient.amount > 0) {
|
|
830
|
+
throw new StackableUnitAmountException()
|
|
831
|
+
}
|
|
832
|
+
return unitCount
|
|
833
|
+
}
|
|
834
|
+
return recipient.amount ?? 0
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
const total = amounts.reduce((sum, amount) => sum + amount, 0)
|
|
838
|
+
|
|
839
|
+
// Resolve the source (queryBalance now carries tokenUnits, so the split below sees real units)
|
|
840
|
+
if (sourceWallet === null) {
|
|
841
|
+
sourceWallet = await this.querySourceWallet({
|
|
842
|
+
token,
|
|
843
|
+
amount: total
|
|
844
|
+
})
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (sourceWallet === null || Decimal.cmp(Number(sourceWallet.balance), total) < 0) {
|
|
848
|
+
throw new TransferBalanceException()
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Build a shadow recipient wallet per destination + assign a distinct batch id
|
|
852
|
+
const recipientWallets: Wallet[] = recipients.map(recipient => {
|
|
853
|
+
const recipientWallet = Wallet.create({
|
|
854
|
+
bundle: recipient.bundleHash,
|
|
855
|
+
token
|
|
856
|
+
})
|
|
857
|
+
if (recipient.batchId !== null && recipient.batchId !== undefined) {
|
|
858
|
+
recipientWallet.batchId = recipient.batchId
|
|
859
|
+
} else {
|
|
860
|
+
recipientWallet.initBatchId({ sourceWallet: sourceWallet as Wallet })
|
|
861
|
+
}
|
|
862
|
+
return recipientWallet
|
|
863
|
+
})
|
|
864
|
+
|
|
865
|
+
// Canonical remainder (carries the source's token/characters; reuses the source batch id)
|
|
866
|
+
const remainderWallet = sourceWallet.createRemainder(this.getSecret())
|
|
867
|
+
|
|
868
|
+
// Stackable (NFT): partition the source's tokenUnits across source (SENT union), each
|
|
869
|
+
// recipient (its subset), and remainder (KEPT) BEFORE the molecule is built. No-op for fungible.
|
|
870
|
+
if (recipients.some(recipient => recipient.units && recipient.units.length > 0)) {
|
|
871
|
+
sourceWallet.splitUnitsMulti(
|
|
872
|
+
recipients.map(recipient => recipient.units ?? []),
|
|
873
|
+
recipientWallets,
|
|
874
|
+
remainderWallet
|
|
875
|
+
)
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const molecule = await this.createMolecule({
|
|
879
|
+
sourceWallet,
|
|
880
|
+
remainderWallet
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
const query = await this.createMoleculeMutation({
|
|
884
|
+
mutationClass: MutationTransferTokens,
|
|
885
|
+
molecule
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
query.fillMoleculeMulti({
|
|
889
|
+
recipientWallets,
|
|
890
|
+
amounts
|
|
891
|
+
})
|
|
892
|
+
|
|
893
|
+
return await this.executeQuery(query)
|
|
894
|
+
}
|
|
895
|
+
|
|
763
896
|
/**
|
|
764
897
|
* Request authorization token (guest or profile)
|
|
765
898
|
*
|
|
@@ -1395,51 +1528,6 @@ export default class KnishIOClient {
|
|
|
1395
1528
|
}) as Promise<Response>
|
|
1396
1529
|
}
|
|
1397
1530
|
|
|
1398
|
-
/**
|
|
1399
|
-
* Query user activity
|
|
1400
|
-
*/
|
|
1401
|
-
async queryUserActivity({
|
|
1402
|
-
bundleHash,
|
|
1403
|
-
metaType,
|
|
1404
|
-
metaId,
|
|
1405
|
-
ipAddress = null,
|
|
1406
|
-
browser = null,
|
|
1407
|
-
osCpu = null,
|
|
1408
|
-
resolution = null,
|
|
1409
|
-
timeZone = null,
|
|
1410
|
-
countBy = null,
|
|
1411
|
-
interval = null
|
|
1412
|
-
}: {
|
|
1413
|
-
bundleHash: BundleHash | string
|
|
1414
|
-
metaType: MetaType | string
|
|
1415
|
-
metaId: MetaId | string
|
|
1416
|
-
ipAddress?: string | null
|
|
1417
|
-
browser?: string | null
|
|
1418
|
-
osCpu?: string | null
|
|
1419
|
-
resolution?: string | null
|
|
1420
|
-
timeZone?: string | null
|
|
1421
|
-
countBy?: string | null
|
|
1422
|
-
interval?: string | null
|
|
1423
|
-
}): Promise<Response> {
|
|
1424
|
-
const query = this.createQuery(QueryUserActivity)
|
|
1425
|
-
|
|
1426
|
-
const variables: Record<string, any> = {
|
|
1427
|
-
bundleHash,
|
|
1428
|
-
metaType,
|
|
1429
|
-
metaId
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
if (ipAddress !== null) variables.ipAddress = ipAddress
|
|
1433
|
-
if (browser !== null) variables.browser = browser
|
|
1434
|
-
if (osCpu !== null) variables.osCpu = osCpu
|
|
1435
|
-
if (resolution !== null) variables.resolution = resolution
|
|
1436
|
-
if (timeZone !== null) variables.timeZone = timeZone
|
|
1437
|
-
if (countBy !== null) variables.countBy = countBy
|
|
1438
|
-
if (interval !== null) variables.interval = interval
|
|
1439
|
-
|
|
1440
|
-
return this.executeQuery(query, variables) as Promise<Response>
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
1531
|
/**
|
|
1444
1532
|
* Query token information
|
|
1445
1533
|
*/
|
|
@@ -1579,7 +1667,7 @@ export default class KnishIOClient {
|
|
|
1579
1667
|
amount = null,
|
|
1580
1668
|
meta = null,
|
|
1581
1669
|
batchId = null,
|
|
1582
|
-
units
|
|
1670
|
+
units = null
|
|
1583
1671
|
}: {
|
|
1584
1672
|
token: TokenSlug | string
|
|
1585
1673
|
amount?: number | string | null
|
|
@@ -1589,6 +1677,33 @@ export default class KnishIOClient {
|
|
|
1589
1677
|
}): Promise<Response> {
|
|
1590
1678
|
this.log('info', `KnishIOClient::createToken() - Creating token ${token}...`)
|
|
1591
1679
|
|
|
1680
|
+
const tokenMeta: Record<string, any> = meta || {}
|
|
1681
|
+
const fungibility = tokenMeta.fungibility
|
|
1682
|
+
let resolvedAmount: number | string = amount ?? 0
|
|
1683
|
+
|
|
1684
|
+
// For a stackable token, ensure a batch ID is present (mirror JS createToken)
|
|
1685
|
+
if (fungibility === 'stackable') {
|
|
1686
|
+
tokenMeta.batchId = batchId || generateBatchId({})
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
// Token-unit initialization for stackable / nonfungible tokens: the unit IDs ARE the supply.
|
|
1690
|
+
// Meta values are sent as strings (the validator's MetaItemInput.value is a GraphQL String).
|
|
1691
|
+
if (
|
|
1692
|
+
(fungibility === 'stackable' || fungibility === 'nonfungible' || fungibility === 'non-fungible') &&
|
|
1693
|
+
units && units.length > 0
|
|
1694
|
+
) {
|
|
1695
|
+
if (Number(tokenMeta.decimals ?? 0) > 0) {
|
|
1696
|
+
throw new StackableUnitDecimalsException()
|
|
1697
|
+
}
|
|
1698
|
+
if (Number(amount ?? 0) > 0) {
|
|
1699
|
+
throw new StackableUnitAmountException()
|
|
1700
|
+
}
|
|
1701
|
+
resolvedAmount = units.length
|
|
1702
|
+
tokenMeta.splittable = '1'
|
|
1703
|
+
tokenMeta.decimals = '0'
|
|
1704
|
+
tokenMeta.tokenUnits = JSON.stringify(units)
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1592
1707
|
// Create the molecule mutation
|
|
1593
1708
|
const mutation = await this.createMoleculeMutation({ mutationClass: MutationCreateToken })
|
|
1594
1709
|
|
|
@@ -1603,8 +1718,8 @@ export default class KnishIOClient {
|
|
|
1603
1718
|
// Initialize the create token mutation
|
|
1604
1719
|
await mutation.fillMolecule({
|
|
1605
1720
|
recipientWallet,
|
|
1606
|
-
amount:
|
|
1607
|
-
meta
|
|
1721
|
+
amount: resolvedAmount,
|
|
1722
|
+
meta: tokenMeta
|
|
1608
1723
|
})
|
|
1609
1724
|
|
|
1610
1725
|
// Execute the mutation
|
|
@@ -2292,9 +2407,17 @@ export default class KnishIOClient {
|
|
|
2292
2407
|
molecule
|
|
2293
2408
|
})
|
|
2294
2409
|
|
|
2295
|
-
// Initialize the profile auth mutation
|
|
2410
|
+
// Initialize the profile auth mutation.
|
|
2411
|
+
// PQ-transport Phase E: convey the AUTH source wallet's ML-KEM768 public key as a SIGNED
|
|
2412
|
+
// walletPubkey U-atom meta, so the validator can encrypt CipherHash responses back to THIS
|
|
2413
|
+
// wallet (the one that decrypts them). Signed → MITM can't swap the target. Conveyed
|
|
2414
|
+
// unconditionally (parity with JS/Kotlin/PHP), so even a plaintext auth primes enc_pubkey.
|
|
2415
|
+
const authMeta: Record<string, string> = { encrypt: encrypt ? 'true' : 'false' }
|
|
2416
|
+
if (wallet.pubkey) {
|
|
2417
|
+
authMeta.walletPubkey = wallet.pubkey
|
|
2418
|
+
}
|
|
2296
2419
|
mutation.fillMolecule({
|
|
2297
|
-
meta:
|
|
2420
|
+
meta: authMeta
|
|
2298
2421
|
})
|
|
2299
2422
|
|
|
2300
2423
|
// Execute the mutation
|
package/src/constants.ts
CHANGED
package/src/core/Molecule.ts
CHANGED
|
@@ -319,6 +319,59 @@ export default class Molecule {
|
|
|
319
319
|
return this
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Initialize a MULTI-recipient V-type molecule: one source debits its FULL balance to fund
|
|
324
|
+
* N recipients (each its own amount + stackable units) plus a remainder back to the sender.
|
|
325
|
+
* Multi-recipient sibling of initValue (WP line 544: fund multiple recipients with one
|
|
326
|
+
* transaction). recipientWallets is parallel to amounts.
|
|
327
|
+
*/
|
|
328
|
+
initValues({
|
|
329
|
+
recipientWallets,
|
|
330
|
+
amounts
|
|
331
|
+
}: {
|
|
332
|
+
recipientWallets: Wallet[]
|
|
333
|
+
amounts: number[]
|
|
334
|
+
}): Molecule {
|
|
335
|
+
if (!this.sourceWallet) {
|
|
336
|
+
throw new Error('Source wallet required for value transfer')
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const total = amounts.reduce((sum, amount) => sum + amount, 0)
|
|
340
|
+
|
|
341
|
+
if (Number(this.sourceWallet.balance) - total < 0) {
|
|
342
|
+
throw new BalanceInsufficientException()
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Source atom: debit the ENTIRE balance (UTXO drain); carries the SENT union of token units
|
|
346
|
+
this.addAtom(Atom.create({
|
|
347
|
+
isotope: 'V',
|
|
348
|
+
wallet: this.sourceWallet,
|
|
349
|
+
value: -Number(this.sourceWallet.balance)
|
|
350
|
+
}))
|
|
351
|
+
|
|
352
|
+
// One atom per recipient: +amount_i, walletBundle -> recipient bundle, its own SENT units
|
|
353
|
+
recipientWallets.forEach((recipientWallet, i) => {
|
|
354
|
+
this.addAtom(Atom.create({
|
|
355
|
+
isotope: 'V',
|
|
356
|
+
wallet: recipientWallet,
|
|
357
|
+
value: amounts[i]!,
|
|
358
|
+
metaType: 'walletBundle',
|
|
359
|
+
metaId: recipientWallet.bundle!
|
|
360
|
+
}))
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
// Remainder atom: +(balance - total), walletBundle -> sender bundle, KEPT units
|
|
364
|
+
this.addAtom(Atom.create({
|
|
365
|
+
isotope: 'V',
|
|
366
|
+
wallet: this.remainderWallet!,
|
|
367
|
+
value: Number(this.sourceWallet.balance) - total,
|
|
368
|
+
metaType: 'walletBundle',
|
|
369
|
+
metaId: this.remainderWallet!.bundle!
|
|
370
|
+
}))
|
|
371
|
+
|
|
372
|
+
return this
|
|
373
|
+
}
|
|
374
|
+
|
|
322
375
|
/**
|
|
323
376
|
* Sign the molecule with one-time signature
|
|
324
377
|
* Matches JavaScript SDK sign method
|
package/src/core/Wallet.ts
CHANGED
|
@@ -349,6 +349,42 @@ export default class Wallet {
|
|
|
349
349
|
remainderWallet.tokenUnits = remainderTokenUnits
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
/**
|
|
353
|
+
* Split token units across MULTIPLE recipients (N-way sibling of splitUnits).
|
|
354
|
+
*
|
|
355
|
+
* The source retains the SENT union (all units leaving), each recipient gets its own
|
|
356
|
+
* subset, and the remainder keeps the KEPT units (those not assigned to any recipient).
|
|
357
|
+
* recipientUnitLists is parallel to recipientWallets. No-op when no units are sent.
|
|
358
|
+
*
|
|
359
|
+
* @param recipientUnitLists - per-recipient arrays of token unit IDs (parallel to recipientWallets)
|
|
360
|
+
* @param recipientWallets - destination wallets
|
|
361
|
+
* @param remainderWallet - wallet to receive the KEPT units
|
|
362
|
+
*/
|
|
363
|
+
splitUnitsMulti(
|
|
364
|
+
recipientUnitLists: string[][],
|
|
365
|
+
recipientWallets: Wallet[],
|
|
366
|
+
remainderWallet: Wallet
|
|
367
|
+
): void {
|
|
368
|
+
const sentIds = new Set<string>(recipientUnitLists.flat())
|
|
369
|
+
|
|
370
|
+
// Nothing to split (fungible transfer) — leave token units untouched
|
|
371
|
+
if (sentIds.size === 0) {
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Each recipient gets its own subset of the source's token units
|
|
376
|
+
recipientWallets.forEach((recipientWallet, i) => {
|
|
377
|
+
const ids = recipientUnitLists[i] ?? []
|
|
378
|
+
recipientWallet.tokenUnits = this.tokenUnits.filter(tokenUnit => ids.includes(tokenUnit.id))
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
// The remainder keeps everything not sent to any recipient (KEPT)
|
|
382
|
+
remainderWallet.tokenUnits = this.tokenUnits.filter(tokenUnit => !sentIds.has(tokenUnit.id))
|
|
383
|
+
|
|
384
|
+
// The source carries the SENT union (the ownership authority the validator reads)
|
|
385
|
+
this.tokenUnits = this.tokenUnits.filter(tokenUnit => sentIds.has(tokenUnit.id))
|
|
386
|
+
}
|
|
387
|
+
|
|
352
388
|
/**
|
|
353
389
|
* Get token units data
|
|
354
390
|
* Stub for compatibility
|
|
@@ -410,6 +446,16 @@ export default class Wallet {
|
|
|
410
446
|
}
|
|
411
447
|
|
|
412
448
|
async decryptMessage(encryptedData: { cipherText: string; encryptedMessage: string }): Promise<any> {
|
|
449
|
+
const decryptedString = await this._mlkemDecryptToString(encryptedData)
|
|
450
|
+
return decryptedString === null ? null : JSON.parse(decryptedString)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* ML-KEM768 decapsulate + AES-256-GCM decrypt → the RAW decrypted UTF-8 string (no JSON.parse).
|
|
455
|
+
* Shared by {@link decryptMessage} (which JSON.parses the result) and the PQ CipherHash transport
|
|
456
|
+
* ({@link decryptMyMessageML768}, which needs the raw response JSON text). PQ-transport Phase E.
|
|
457
|
+
*/
|
|
458
|
+
async _mlkemDecryptToString(encryptedData: { cipherText: string; encryptedMessage: string }): Promise<string | null> {
|
|
413
459
|
const { cipherText, encryptedMessage } = encryptedData
|
|
414
460
|
let sharedSecret
|
|
415
461
|
try {
|
|
@@ -438,9 +484,8 @@ export default class Wallet {
|
|
|
438
484
|
console.info('Wallet::decryptMessage() - deserialized encrypted message', deserializedEncryptedMessage)
|
|
439
485
|
return null
|
|
440
486
|
}
|
|
441
|
-
let decryptedString
|
|
442
487
|
try {
|
|
443
|
-
|
|
488
|
+
return new TextDecoder().decode(decryptedUint8)
|
|
444
489
|
} catch (e) {
|
|
445
490
|
console.warn('Wallet::decryptMessage() - Decoding failed', e)
|
|
446
491
|
console.info('Wallet::decryptMessage() - my public key', this.pubkey)
|
|
@@ -449,7 +494,43 @@ export default class Wallet {
|
|
|
449
494
|
console.info('Wallet::decryptMessage() - decrypted Uint8Array', decryptedUint8)
|
|
450
495
|
return null
|
|
451
496
|
}
|
|
452
|
-
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Canonical cross-SDK hashShare for a public key: standard base64 of SHAKE256(pubkey_utf8, 8 bytes)
|
|
501
|
+
* — byte-matches the validator's hash_share and the JS/Kotlin/PHP hashShare. `shake256(pubkey, 64)`
|
|
502
|
+
* = 64 bits = 8 bytes (hex) → hex-decode → standard base64 via serializeKey. PQ-transport Phase E.
|
|
503
|
+
*/
|
|
504
|
+
hashShare(pubkey: string): string {
|
|
505
|
+
const hex = shake256(pubkey, 64)
|
|
506
|
+
const bytes = new Uint8Array(hex.length / 2)
|
|
507
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
508
|
+
bytes[i] = parseInt(hex.substr(i * 2, 2), 16)
|
|
509
|
+
}
|
|
510
|
+
return this.serializeKey(bytes)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Post-quantum (ML-KEM768) CipherHash request envelope: a stringified single-recipient map
|
|
515
|
+
* `{ "<hashShare(recipientPubkey)>": {cipherText, encryptedMessage} }` (object-valued, via
|
|
516
|
+
* {@link encryptMessage}). Matches the Rust validator's CipherHash handler. PQ-transport Phase E.
|
|
517
|
+
*/
|
|
518
|
+
async encryptStringML768(message: any, recipientPubkey: string): Promise<string> {
|
|
519
|
+
const envelope = await this.encryptMessage(message, recipientPubkey)
|
|
520
|
+
return JSON.stringify({ [this.hashShare(recipientPubkey)]: envelope })
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Decrypt a CipherHash response map addressed to THIS wallet's ML-KEM pubkey
|
|
525
|
+
* (`hashShare(this.pubkey)`) → the RAW decrypted GraphQL response JSON text (NOT JSON.parsed;
|
|
526
|
+
* it replaces the HTTP response body for the normal parser). `null` if no entry / decrypt fails.
|
|
527
|
+
*/
|
|
528
|
+
async decryptMyMessageML768(map: Record<string, { cipherText: string; encryptedMessage: string }>): Promise<string | null> {
|
|
529
|
+
const envelope = map[this.hashShare(this.pubkey)]
|
|
530
|
+
if (!envelope) {
|
|
531
|
+
return null
|
|
532
|
+
}
|
|
533
|
+
return this._mlkemDecryptToString(envelope)
|
|
453
534
|
}
|
|
454
535
|
|
|
455
536
|
// =============================================================================
|
package/src/libraries/Decimal.ts
CHANGED
|
@@ -72,7 +72,7 @@ export default class Decimal {
|
|
|
72
72
|
* @param debug Enable debug logging
|
|
73
73
|
* @returns 0 if equal, 1 if value1 > value2, -1 if value1 < value2
|
|
74
74
|
*/
|
|
75
|
-
static cmp(value1: number, value2: number,
|
|
75
|
+
static cmp(value1: number, value2: number, _debug: boolean = false): number {
|
|
76
76
|
const val1 = Decimal.val(value1) * MULTIPLIER
|
|
77
77
|
const val2 = Decimal.val(value2) * MULTIPLIER
|
|
78
78
|
|