@sphereon/ssi-sdk.oid4vci-holder 0.34.1-next.3 → 0.34.1-next.322

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.
@@ -1,16 +1,15 @@
1
1
  import { LOG } from '@sphereon/oid4vci-client'
2
2
  import {
3
+ AuthorizationChallengeCodeResponse,
3
4
  CredentialConfigurationSupported,
4
- CredentialSupportedSdJwtVc,
5
- CredentialConfigurationSupportedSdJwtVcV1_0_13,
6
- CredentialOfferFormatV1_0_11,
5
+ CredentialConfigurationSupportedSdJwtVcV1_0_15,
7
6
  CredentialResponse,
7
+ CredentialResponseV1_0_15,
8
+ CredentialSupportedSdJwtVc,
8
9
  getSupportedCredentials,
9
10
  getTypesFromCredentialSupported,
10
11
  getTypesFromObject,
11
12
  MetadataDisplay,
12
- OpenId4VCIVersion,
13
- AuthorizationChallengeCodeResponse,
14
13
  } from '@sphereon/oid4vci-common'
15
14
  import { KeyUse } from '@sphereon/ssi-sdk-ext.did-resolver-jwk'
16
15
  import { getOrCreatePrimaryIdentifier, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils'
@@ -23,7 +22,8 @@ import {
23
22
  managedIdentifierToJwk,
24
23
  } from '@sphereon/ssi-sdk-ext.identifier-resolution'
25
24
  import { keyTypeFromCryptographicSuite } from '@sphereon/ssi-sdk-ext.key-utils'
26
- import { IBasicCredentialLocaleBranding, IBasicIssuerLocaleBranding } from '@sphereon/ssi-sdk.data-store'
25
+ import { defaultHasher } from '@sphereon/ssi-sdk.core'
26
+ import { IBasicCredentialLocaleBranding, IBasicIssuerLocaleBranding } from '@sphereon/ssi-sdk.data-store-types'
27
27
  import {
28
28
  CredentialMapper,
29
29
  Hasher,
@@ -40,8 +40,12 @@ import {
40
40
  } from '@sphereon/ssi-types'
41
41
  import { asArray } from '@veramo/utils'
42
42
  import { translate } from '../localization/Localization'
43
+ import { FirstPartyMachine } from '../machines/firstPartyMachine'
44
+ import { issuerLocaleBrandingFrom, oid4vciGetCredentialBrandingFrom, sdJwtGetCredentialBrandingFrom } from '../mappers/OIDC4VCIBrandingMapper'
45
+ import { FirstPartyMachineState, FirstPartyMachineStateTypes } from '../types/FirstPartyMachine'
43
46
  import {
44
47
  DidAgents,
48
+ GetBasicIssuerLocaleBrandingArgs,
45
49
  GetCredentialBrandingArgs,
46
50
  GetCredentialConfigsSupportedArgs,
47
51
  GetCredentialConfigsSupportedBySingleTypeOrIdArgs,
@@ -49,22 +53,17 @@ import {
49
53
  GetIssuanceCryptoSuiteArgs,
50
54
  GetIssuanceDidMethodArgs,
51
55
  GetIssuanceOptsArgs,
52
- GetBasicIssuerLocaleBrandingArgs,
53
56
  GetPreferredCredentialFormatsArgs,
54
57
  IssuanceOpts,
55
58
  MapCredentialToAcceptArgs,
56
59
  MappedCredentialToAccept,
57
60
  OID4VCIHolderEvent,
61
+ RequiredContext,
58
62
  SelectAppLocaleBrandingArgs,
63
+ StartFirstPartApplicationMachine,
59
64
  VerificationResult,
60
65
  VerifyCredentialToAcceptArgs,
61
- StartFirstPartApplicationMachine,
62
- RequiredContext,
63
66
  } from '../types/IOID4VCIHolder'
64
- import { oid4vciGetCredentialBrandingFrom, sdJwtGetCredentialBrandingFrom, issuerLocaleBrandingFrom } from '../mappers/OIDC4VCIBrandingMapper'
65
- import { FirstPartyMachine } from '../machines/firstPartyMachine'
66
- import { FirstPartyMachineState, FirstPartyMachineStateTypes } from '../types/FirstPartyMachine'
67
- import { defaultHasher } from '@sphereon/ssi-sdk.core'
68
67
 
69
68
  export const getCredentialBranding = async (args: GetCredentialBrandingArgs): Promise<Record<string, Array<IBasicCredentialLocaleBranding>>> => {
70
69
  const { credentialsSupported, context } = args
@@ -72,8 +71,8 @@ export const getCredentialBranding = async (args: GetCredentialBrandingArgs): Pr
72
71
  await Promise.all(
73
72
  Object.entries(credentialsSupported).map(async ([configId, credentialsConfigSupported]): Promise<void> => {
74
73
  let sdJwtTypeMetadata: SdJwtTypeMetadata | undefined
75
- if (credentialsConfigSupported.format === 'vc+sd-jwt') {
76
- const vct = (<CredentialSupportedSdJwtVc | CredentialConfigurationSupportedSdJwtVcV1_0_13>credentialsConfigSupported).vct
74
+ if (credentialsConfigSupported.format === 'dc+sd-jwt') {
75
+ const vct = (<CredentialSupportedSdJwtVc | CredentialConfigurationSupportedSdJwtVcV1_0_15>credentialsConfigSupported).vct
77
76
  if (vct.startsWith('http')) {
78
77
  try {
79
78
  sdJwtTypeMetadata = await context.agent.fetchSdJwtTypeMetadataFromVctUrl({ vct })
@@ -153,10 +152,7 @@ export const selectCredentialLocaleBranding = async (
153
152
  export const verifyCredentialToAccept = async (args: VerifyCredentialToAcceptArgs): Promise<VerificationResult> => {
154
153
  const { mappedCredential, hasher, onVerifyEBSICredentialIssuer, schemaValidation, context } = args
155
154
 
156
- const credential = mappedCredential.credentialToAccept.credentialResponse.credential as OriginalVerifiableCredential
157
- if (!credential) {
158
- return Promise.reject(Error('No credential found in credential response'))
159
- }
155
+ const credential = extractCredentialFromResponse(mappedCredential.credentialToAccept.credentialResponse)
160
156
 
161
157
  const wrappedVC = CredentialMapper.toWrappedVerifiableCredential(credential, { hasher: hasher ?? defaultHasher })
162
158
  if (
@@ -205,11 +201,7 @@ export const verifyCredentialToAccept = async (args: VerifyCredentialToAcceptArg
205
201
  export const mapCredentialToAccept = async (args: MapCredentialToAcceptArgs): Promise<MappedCredentialToAccept> => {
206
202
  const { credentialToAccept, hasher } = args
207
203
 
208
- const credentialResponse: CredentialResponse = credentialToAccept.credentialResponse
209
- const verifiableCredential: W3CVerifiableCredential | undefined = credentialResponse.credential
210
- if (!verifiableCredential) {
211
- return Promise.reject(Error('No credential found in credential response'))
212
- }
204
+ const verifiableCredential = extractCredentialFromResponse(credentialToAccept.credentialResponse) as W3CVerifiableCredential
213
205
 
214
206
  const wrappedVerifiableCredential: WrappedVerifiableCredential = CredentialMapper.toWrappedVerifiableCredential(
215
207
  verifiableCredential as OriginalVerifiableCredential,
@@ -217,9 +209,7 @@ export const mapCredentialToAccept = async (args: MapCredentialToAcceptArgs): Pr
217
209
  )
218
210
  let uniformVerifiableCredential: IVerifiableCredential
219
211
  if (CredentialMapper.isSdJwtDecodedCredential(wrappedVerifiableCredential.credential)) {
220
- uniformVerifiableCredential = await sdJwtDecodedCredentialToUniformCredential(
221
- <SdJwtDecodedVerifiableCredential>wrappedVerifiableCredential.credential,
222
- )
212
+ uniformVerifiableCredential = sdJwtDecodedCredentialToUniformCredential(<SdJwtDecodedVerifiableCredential>wrappedVerifiableCredential.credential)
223
213
  } else if (CredentialMapper.isSdJwtEncoded(wrappedVerifiableCredential.credential)) {
224
214
  if (!hasher) {
225
215
  return Promise.reject('a hasher is required for encoded SD-JWT credentials')
@@ -240,6 +230,7 @@ export const mapCredentialToAccept = async (args: MapCredentialToAcceptArgs): Pr
240
230
  ? uniformVerifiableCredential.decodedPayload.iss
241
231
  : uniformVerifiableCredential.issuer.id
242
232
 
233
+ const credentialResponse = credentialToAccept.credentialResponse as CredentialResponseV1_0_15
243
234
  return {
244
235
  correlationId,
245
236
  credentialToAccept,
@@ -250,6 +241,27 @@ export const mapCredentialToAccept = async (args: MapCredentialToAcceptArgs): Pr
250
241
  }
251
242
  }
252
243
 
244
+ export const extractCredentialFromResponse = (credentialResponse: CredentialResponse): OriginalVerifiableCredential => {
245
+ let credential: OriginalVerifiableCredential | undefined
246
+
247
+ if ('credential' in credentialResponse) {
248
+ credential = credentialResponse.credential as OriginalVerifiableCredential
249
+ } else if (
250
+ 'credentials' in credentialResponse &&
251
+ credentialResponse.credentials &&
252
+ Array.isArray(credentialResponse.credentials) &&
253
+ credentialResponse.credentials.length > 0
254
+ ) {
255
+ credential = credentialResponse.credentials[0].credential as OriginalVerifiableCredential // FIXME SSISDK-13 (no multi-credential support yet)
256
+ }
257
+
258
+ if (!credential) {
259
+ throw new Error('No credential found in credential response')
260
+ }
261
+
262
+ return credential
263
+ }
264
+
253
265
  export const getIdentifierOpts = async (args: GetIdentifierArgs): Promise<ManagedIdentifierResult> => {
254
266
  const { issuanceOpt, context } = args
255
267
  const { identifier: identifierArg } = issuanceOpt
@@ -370,7 +382,7 @@ export const getCredentialConfigsSupportedBySingleTypeOrId = async (
370
382
  }
371
383
 
372
384
  if (configurationId) {
373
- const allSupported = client.getCredentialsSupported(false)
385
+ const allSupported = client.getCredentialsSupported(undefined, format)
374
386
  return Object.fromEntries(
375
387
  Object.entries(allSupported).filter(
376
388
  ([id, supported]) => id === configurationId || supported.id === configurationId || createIdFromTypes(supported) === configurationId,
@@ -378,29 +390,15 @@ export const getCredentialConfigsSupportedBySingleTypeOrId = async (
378
390
  )
379
391
  }
380
392
 
381
- if (!types && !client.credentialOffer) {
382
- return Promise.reject(Error('openID4VCIClient has no credentialOffer and no types where provided'))
383
- /*} else if (!format && !client.credentialOffer) {
384
- return Promise.reject(Error('openID4VCIClient has no credentialOffer and no formats where provided'))*/
393
+ if (!client.credentialOffer) {
394
+ return Promise.reject(Error('openID4VCIClient has no credentialOffer'))
385
395
  }
386
- // We should always have a credential offer at this point given the above
387
- if (!Array.isArray(format) && client.credentialOffer) {
388
- if (
389
- client.version() > OpenId4VCIVersion.VER_1_0_09 &&
390
- typeof client.credentialOffer.credential_offer === 'object' &&
391
- 'credentials' in client.credentialOffer.credential_offer
392
- ) {
393
- format = client.credentialOffer.credential_offer.credentials
394
- .filter((cred: CredentialOfferFormatV1_0_11 | string) => typeof cred !== 'string')
395
- .map((cred: CredentialOfferFormatV1_0_11 | string) => (cred as CredentialOfferFormatV1_0_11).format)
396
- if (format?.length === 0) {
397
- format = undefined // Otherwise we would match nothing
398
- }
399
- }
396
+ if (!types) {
397
+ return Promise.reject(Error('openID4VCIClient has no types'))
400
398
  }
401
399
 
402
400
  const offerSupported = getSupportedCredentials({
403
- types: types ? [types] : client.getCredentialOfferTypes(),
401
+ types: [types],
404
402
  format,
405
403
  version: client.version(),
406
404
  issuerMetadata: client.endpointMetadata.credentialIssuerMetadata,
@@ -580,7 +578,8 @@ export const getIssuanceCryptoSuite = async (opts: GetIssuanceCryptoSuiteArgs):
580
578
  case 'jwt':
581
579
  case 'jwt_vc_json':
582
580
  case 'jwt_vc':
583
- case 'vc+sd-jwt':
581
+ //case 'vc+sd-jwt': // TODO see SSISDK-52 concerning vc+sd-jwt
582
+ case 'dc+sd-jwt':
584
583
  case 'mso_mdoc': {
585
584
  const supportedPreferences: Array<JoseSignatureAlgorithm | JoseSignatureAlgorithmString> = jwtCryptographicSuitePreferences.filter(
586
585
  (suite: JoseSignatureAlgorithm | JoseSignatureAlgorithmString) => signing_algs_supported.includes(suite),
@@ -1,10 +1,11 @@
1
- import { BaseActionObject, Interpreter, ResolveTypegenMeta, ServiceMap, State, StateMachine, StatesConfig, TypegenDisabled } from 'xstate'
1
+ import { RPRegistrationMetadataPayload } from '@sphereon/did-auth-siop'
2
2
  import { OpenID4VCIClientState } from '@sphereon/oid4vci-client'
3
- import { DidAuthConfig, Party } from '@sphereon/ssi-sdk.data-store'
4
- import { PresentationDefinitionWithLocation, RPRegistrationMetadataPayload } from '@sphereon/did-auth-siop'
5
- import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store'
6
3
  import { AuthorizationChallengeCodeResponse } from '@sphereon/oid4vci-common'
4
+ import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store'
5
+ import { DidAuthConfig, Party } from '@sphereon/ssi-sdk.data-store-types'
7
6
  import { IIdentifier } from '@veramo/core'
7
+ import { DcqlQuery } from 'dcql'
8
+ import { BaseActionObject, Interpreter, ResolveTypegenMeta, ServiceMap, State, StateMachine, StatesConfig, TypegenDisabled } from 'xstate'
8
9
  import { ErrorDetails, RequiredContext } from './IOID4VCIHolder'
9
10
 
10
11
  export enum FirstPartyMachineStateTypes {
@@ -149,7 +150,7 @@ export type SiopV2AuthorizationRequestData = {
149
150
  clientIdScheme?: string
150
151
  clientId?: string
151
152
  entityId?: string
152
- presentationDefinitions?: PresentationDefinitionWithLocation[]
153
+ dcqlQuery: DcqlQuery
153
154
  }
154
155
 
155
156
  export type FirstPartyMachineNavigationArgs = {
@@ -1,4 +1,5 @@
1
- import { OpenID4VCIClient, OpenID4VCIClientState } from '@sphereon/oid4vci-client'
1
+ import { DynamicRegistrationClientMetadata } from '@sphereon/oid4vc-common'
2
+ import { OpenID4VCIClientState, OpenID4VCIClientV1_0_15 } from '@sphereon/oid4vci-client'
2
3
  import {
3
4
  AuthorizationRequestOpts,
4
5
  AuthorizationResponse,
@@ -7,6 +8,7 @@ import {
7
8
  CredentialConfigurationSupported,
8
9
  CredentialOfferRequestWithBaseUrl,
9
10
  CredentialResponse,
11
+ CredentialResponseV1_0_15,
10
12
  CredentialsSupportedDisplay,
11
13
  EndpointMetadataResult,
12
14
  ExperimentalSubjectIssuance,
@@ -14,7 +16,6 @@ import {
14
16
  MetadataDisplay,
15
17
  NotificationRequest,
16
18
  } from '@sphereon/oid4vci-common'
17
- import { DynamicRegistrationClientMetadata } from '@sphereon/oid4vc-common'
18
19
  import { CreateOrGetIdentifierOpts, IdentifierProviderOpts, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils'
19
20
  import {
20
21
  IIdentifierResolution,
@@ -25,6 +26,7 @@ import {
25
26
  import { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
26
27
  import { IContactManager } from '@sphereon/ssi-sdk.contact-manager'
27
28
  import { ICredentialStore } from '@sphereon/ssi-sdk.credential-store'
29
+ import { ICredentialValidation, SchemaValidation } from '@sphereon/ssi-sdk.credential-validation'
28
30
  import {
29
31
  DigitalCredential,
30
32
  IBasicCredentialClaim,
@@ -33,11 +35,10 @@ import {
33
35
  Identity,
34
36
  IIssuerLocaleBranding,
35
37
  Party,
36
- } from '@sphereon/ssi-sdk.data-store'
38
+ } from '@sphereon/ssi-sdk.data-store-types'
37
39
  import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding'
38
40
  import { ImDLMdoc } from '@sphereon/ssi-sdk.mdl-mdoc'
39
41
  import { ISDJwtPlugin } from '@sphereon/ssi-sdk.sd-jwt'
40
- import { ICredentialValidation, SchemaValidation } from '@sphereon/ssi-sdk.credential-validation'
41
42
  import { IDidAuthSiopOpAuthenticator } from '@sphereon/ssi-sdk.siopv2-oid4vp-op-auth'
42
43
  import {
43
44
  HasherSync,
@@ -78,6 +79,7 @@ export interface IOID4VCIHolder extends IPluginMethodMap {
78
79
  context: RequiredContext,
79
80
  ): Promise<Array<CredentialToSelectFromResult>>
80
81
 
82
+ oid4vciHolderPrepareAuthorizationRequest(args: PrepareAuthorizationRequestArgs, context: RequiredContext): Promise<PrepareAuthorizationResult>
81
83
  oid4vciHolderGetContact(args: GetContactArgs, context: RequiredContext): Promise<Party | undefined>
82
84
 
83
85
  oid4vciHolderGetCredentials(args: GetCredentialsArgs, context: RequiredContext): Promise<Array<MappedCredentialToAccept>>
@@ -147,6 +149,7 @@ export type PrepareStartArgs = Pick<
147
149
  OID4VCIMachineContext,
148
150
  'requestData' | 'authorizationRequestOpts' | 'didMethodPreferences' | 'issuanceOpt' | 'accessTokenOpts'
149
151
  >
152
+ export type PrepareAuthorizationRequestArgs = Pick<OID4VCIMachineContext, 'openID4VCIClientState' | 'contact'>
150
153
  export type CreateCredentialsToSelectFromArgs = Pick<
151
154
  OID4VCIMachineContext,
152
155
  'credentialsSupported' | 'credentialBranding' | 'selectedCredentials' | 'locale' | 'openID4VCIClientState'
@@ -255,6 +258,7 @@ export enum OID4VCIMachineStates {
255
258
  selectCredentials = 'selectCredentials',
256
259
  transitionFromSelectingCredentials = 'transitionFromSelectingCredentials',
257
260
  verifyPin = 'verifyPin',
261
+ prepareAuthorizationRequest = 'prepareAuthorizationRequest',
258
262
  initiateAuthorizationRequest = 'initiateAuthorizationRequest',
259
263
  waitForAuthorizationResponse = 'waitForAuthorizationResponse',
260
264
  getCredentials = 'getCredentials',
@@ -377,6 +381,7 @@ export enum OID4VCIMachineGuards {
377
381
  requirePinGuard = 'oid4vciRequirePinGuard',
378
382
  requireAuthorizationGuard = 'oid4vciRequireAuthorizationGuard',
379
383
  noAuthorizationGuard = 'oid4vciNoAuthorizationGuard',
384
+ hasNonceEndpointGuard = 'oid4vciHasNonceEndpointGuard ',
380
385
  hasAuthorizationResponse = 'oid4vciHasAuthorizationResponse',
381
386
  hasNoContactIdentityGuard = 'oid4vciHasNoContactIdentityGuard',
382
387
  verificationCodeGuard = 'oid4vciVerificationCodeGuard',
@@ -393,6 +398,7 @@ export enum OID4VCIMachineServices {
393
398
  getFederationTrust = 'getFederationTrust',
394
399
  addContactIdentity = 'addContactIdentity',
395
400
  createCredentialsToSelectFrom = 'createCredentialsToSelectFrom',
401
+ prepareAuthorizationRequest = 'prepareAuthorizationRequest',
396
402
  getIssuerBranding = 'getIssuerBranding',
397
403
  storeIssuerBranding = 'storeIssuerBranding',
398
404
  getCredentials = 'getCredentials',
@@ -458,13 +464,16 @@ export type OID4VCIMachine = {
458
464
  }
459
465
 
460
466
  export type StartResult = {
461
- authorizationCodeURL?: string
462
467
  credentialBranding?: Record<string, Array<IBasicCredentialLocaleBranding>>
463
468
  credentialsSupported: Record<string, CredentialConfigurationSupported>
464
469
  serverMetadata: EndpointMetadataResult
465
470
  oid4vciClientState: OpenID4VCIClientState
466
471
  }
467
472
 
473
+ export type PrepareAuthorizationResult = {
474
+ authorizationCodeURL?: string
475
+ }
476
+
468
477
  export type SelectAppLocaleBrandingArgs = {
469
478
  locale?: string
470
479
  localeBranding?: Array<IBasicCredentialLocaleBranding | IBasicIssuerLocaleBranding>
@@ -501,11 +510,11 @@ export type CredentialToAccept = {
501
510
  id?: string
502
511
  types: string[]
503
512
  issuanceOpt: IssuanceOpts
504
- credentialResponse: CredentialResponse
513
+ credentialResponse: CredentialResponseV1_0_15 | CredentialResponse
505
514
  }
506
515
 
507
516
  export type GetCredentialConfigsSupportedArgs = {
508
- client: OpenID4VCIClient
517
+ client: OpenID4VCIClientV1_0_15
509
518
  vcFormatPreferences: Array<string>
510
519
  format?: Array<string>
511
520
  types?: Array<Array<string>>
@@ -517,7 +526,7 @@ export type GetCredentialConfigsSupportedArgs = {
517
526
  * It can potentially return multiple results mainly because of different formats.
518
527
  */
519
528
  export type GetCredentialConfigsSupportedBySingleTypeOrIdArgs = {
520
- client: OpenID4VCIClient
529
+ client: OpenID4VCIClientV1_0_15
521
530
  vcFormatPreferences: Array<string>
522
531
  format?: string[]
523
532
  types?: string[]
@@ -552,7 +561,7 @@ export type GetDefaultIssuanceOptsArgs = {
552
561
  }
553
562
 
554
563
  export type DefaultIssuanceOpts = {
555
- client: OpenID4VCIClient
564
+ client: OpenID4VCIClientV1_0_15
556
565
  }
557
566
 
558
567
  export type GetIdentifierArgs = {
@@ -589,7 +598,7 @@ export type CreateIdentifierCreateOpts = {
589
598
  }
590
599
 
591
600
  export type GetIssuanceOptsArgs = {
592
- client: OpenID4VCIClient
601
+ client: OpenID4VCIClientV1_0_15
593
602
  credentialsSupported: Record<string, CredentialConfigurationSupported>
594
603
  serverMetadata: EndpointMetadataResult
595
604
  context: RequiredContext
@@ -601,13 +610,13 @@ export type GetIssuanceOptsArgs = {
601
610
 
602
611
  export type GetIssuanceDidMethodArgs = {
603
612
  credentialSupported: CredentialConfigurationSupported
604
- client: OpenID4VCIClient
613
+ client: OpenID4VCIClientV1_0_15
605
614
  didMethodPreferences: Array<SupportedDidMethodEnum>
606
615
  }
607
616
 
608
617
  export type GetIssuanceCryptoSuiteArgs = {
609
618
  credentialSupported: CredentialConfigurationSupported
610
- client: OpenID4VCIClient
619
+ client: OpenID4VCIClientV1_0_15
611
620
  jwtCryptographicSuitePreferences: Array<JoseSignatureAlgorithm | JoseSignatureAlgorithmString>
612
621
  jsonldCryptographicSuitePreferences: Array<string>
613
622
  }
@@ -615,7 +624,7 @@ export type GetIssuanceCryptoSuiteArgs = {
615
624
  export type GetCredentialArgs = {
616
625
  pin?: string
617
626
  issuanceOpt: IssuanceOpts
618
- client: OpenID4VCIClient
627
+ client: OpenID4VCIClientV1_0_15
619
628
  accessTokenOpts?: AccessTokenOpts
620
629
  }
621
630