@btc-vision/transaction 1.3.3 → 1.3.5
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/browser/_version.d.ts +1 -1
- package/browser/index.js +1 -1
- package/browser/opnet.d.ts +7 -0
- package/browser/transaction/TransactionFactory.d.ts +2 -0
- package/browser/transaction/browser/extensions/UnisatSigner.d.ts +3 -5
- package/browser/transaction/builders/SharedInteractionTransaction.d.ts +1 -1
- package/browser/transaction/shared/TweakedTransaction.d.ts +2 -1
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/opnet.d.ts +7 -0
- package/build/transaction/TransactionFactory.d.ts +2 -0
- package/build/transaction/TransactionFactory.js +70 -7
- package/build/transaction/browser/extensions/UnisatSigner.d.ts +3 -5
- package/build/transaction/browser/extensions/UnisatSigner.js +2 -0
- package/build/transaction/builders/DeploymentTransaction.js +12 -2
- package/build/transaction/builders/SharedInteractionTransaction.d.ts +1 -1
- package/build/transaction/builders/SharedInteractionTransaction.js +24 -14
- package/build/transaction/builders/TransactionBuilder.js +3 -3
- package/build/transaction/shared/TweakedTransaction.d.ts +2 -1
- package/build/transaction/shared/TweakedTransaction.js +21 -7
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/opnet.ts +9 -0
- package/src/transaction/TransactionFactory.ts +101 -12
- package/src/transaction/browser/extensions/UnisatSigner.ts +6 -6
- package/src/transaction/builders/DeploymentTransaction.ts +12 -3
- package/src/transaction/builders/SharedInteractionTransaction.ts +31 -28
- package/src/transaction/builders/TransactionBuilder.ts +7 -3
- package/src/transaction/shared/TweakedTransaction.ts +40 -9
|
@@ -19,7 +19,11 @@ import {
|
|
|
19
19
|
} from './interfaces/ITransactionParameters.js';
|
|
20
20
|
import { PSBTTypes } from './psbt/PSBTTypes.js';
|
|
21
21
|
import { ChallengeSolutionTransaction } from './builders/ChallengeSolutionTransaction.js';
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
IDeploymentParametersWithoutSigner,
|
|
24
|
+
InteractionParametersWithoutSigner,
|
|
25
|
+
} from './browser/Web3Provider.js';
|
|
26
|
+
import { WindowWithWallets } from './browser/extensions/UnisatSigner.js';
|
|
23
27
|
|
|
24
28
|
export interface DeploymentResult {
|
|
25
29
|
readonly transaction: [string, string];
|
|
@@ -174,9 +178,11 @@ export class TransactionFactory {
|
|
|
174
178
|
throw new Error('Field "signer" not provided, OP_WALLET not detected.');
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs);
|
|
177
182
|
const preTransaction: InteractionTransaction = new InteractionTransaction({
|
|
178
183
|
...interactionParameters,
|
|
179
184
|
utxos: [interactionParameters.utxos[0]], // we simulate one input here.
|
|
185
|
+
optionalInputs: inputs,
|
|
180
186
|
});
|
|
181
187
|
|
|
182
188
|
// we don't sign that transaction, we just need the parameters.
|
|
@@ -221,11 +227,14 @@ export class TransactionFactory {
|
|
|
221
227
|
|
|
222
228
|
const newParams: IInteractionParameters = {
|
|
223
229
|
...interactionParameters,
|
|
224
|
-
utxos:
|
|
230
|
+
utxos: [
|
|
231
|
+
...this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.to, 0),
|
|
232
|
+
], // always 0
|
|
225
233
|
randomBytes: preTransaction.getRndBytes(),
|
|
226
234
|
preimage: preTransaction.getPreimage(),
|
|
227
235
|
nonWitnessUtxo: signedTransaction.tx.toBuffer(),
|
|
228
236
|
estimatedFees: preTransaction.estimatedFees,
|
|
237
|
+
optionalInputs: inputs,
|
|
229
238
|
};
|
|
230
239
|
|
|
231
240
|
const finalTransaction: InteractionTransaction = new InteractionTransaction(newParams);
|
|
@@ -253,21 +262,46 @@ export class TransactionFactory {
|
|
|
253
262
|
public async signDeployment(
|
|
254
263
|
deploymentParameters: IDeploymentParameters,
|
|
255
264
|
): Promise<DeploymentResult> {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
265
|
+
const opWalletDeployment = await this.detectDeploymentOPWallet(deploymentParameters);
|
|
266
|
+
if (opWalletDeployment) {
|
|
267
|
+
return opWalletDeployment;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!('signer' in deploymentParameters)) {
|
|
271
|
+
throw new Error('Field "signer" not provided, OP_WALLET not detected.');
|
|
272
|
+
}
|
|
259
273
|
|
|
260
|
-
|
|
261
|
-
|
|
274
|
+
const inputs = this.parseOptionalInputs(deploymentParameters.optionalInputs);
|
|
275
|
+
const preTransaction: DeploymentTransaction = new DeploymentTransaction({
|
|
276
|
+
...deploymentParameters,
|
|
277
|
+
utxos: [deploymentParameters.utxos[0]], // we simulate one input here.
|
|
278
|
+
optionalInputs: inputs,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// we don't sign that transaction, we just need the parameters.
|
|
282
|
+
await preTransaction.generateTransactionMinimalSignatures();
|
|
262
283
|
|
|
263
284
|
const parameters: IFundingTransactionParameters =
|
|
264
285
|
await preTransaction.getFundingTransactionParameters();
|
|
265
286
|
|
|
287
|
+
parameters.utxos = deploymentParameters.utxos;
|
|
266
288
|
parameters.amount =
|
|
267
289
|
(await preTransaction.estimateTransactionFees()) +
|
|
268
290
|
this.getPriorityFee(deploymentParameters) +
|
|
269
291
|
preTransaction.getOptionalOutputValue();
|
|
270
292
|
|
|
293
|
+
const feeEstimationFundingTransaction = await this.createFundTransaction({
|
|
294
|
+
...parameters,
|
|
295
|
+
optionalOutputs: [],
|
|
296
|
+
optionalInputs: [],
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (!feeEstimationFundingTransaction) {
|
|
300
|
+
throw new Error('Could not sign funding transaction.');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
parameters.estimatedFees = feeEstimationFundingTransaction.estimatedFees;
|
|
304
|
+
|
|
271
305
|
const fundingTransaction: FundingTransaction = new FundingTransaction({
|
|
272
306
|
...parameters,
|
|
273
307
|
optionalInputs: [],
|
|
@@ -292,12 +326,12 @@ export class TransactionFactory {
|
|
|
292
326
|
|
|
293
327
|
const newParams: IDeploymentParameters = {
|
|
294
328
|
...deploymentParameters,
|
|
295
|
-
utxos: [newUtxo],
|
|
329
|
+
utxos: [newUtxo], // always 0
|
|
296
330
|
randomBytes: preTransaction.getRndBytes(),
|
|
297
331
|
preimage: preTransaction.getPreimage(),
|
|
298
332
|
nonWitnessUtxo: signedTransaction.toBuffer(),
|
|
299
|
-
|
|
300
|
-
optionalInputs:
|
|
333
|
+
estimatedFees: preTransaction.estimatedFees,
|
|
334
|
+
optionalInputs: inputs,
|
|
301
335
|
};
|
|
302
336
|
|
|
303
337
|
const finalTransaction: DeploymentTransaction = new DeploymentTransaction(newParams);
|
|
@@ -396,14 +430,41 @@ export class TransactionFactory {
|
|
|
396
430
|
return utxos;
|
|
397
431
|
}
|
|
398
432
|
|
|
433
|
+
private parseOptionalInputs(optionalInputs?: UTXO[]): UTXO[] {
|
|
434
|
+
return (optionalInputs || []).map((input) => {
|
|
435
|
+
let nonWitness = input.nonWitnessUtxo;
|
|
436
|
+
if (
|
|
437
|
+
nonWitness &&
|
|
438
|
+
!(nonWitness instanceof Uint8Array) &&
|
|
439
|
+
typeof nonWitness === 'object'
|
|
440
|
+
) {
|
|
441
|
+
nonWitness = Buffer.from(
|
|
442
|
+
Uint8Array.from(
|
|
443
|
+
Object.values(input.nonWitnessUtxo as unknown as Record<number, number>),
|
|
444
|
+
),
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
...input,
|
|
450
|
+
nonWitnessUtxo: nonWitness,
|
|
451
|
+
};
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
399
455
|
private async detectInteractionOPWallet(
|
|
400
456
|
interactionParameters: IInteractionParameters | InteractionParametersWithoutSigner,
|
|
401
457
|
): Promise<InteractionResponse | null> {
|
|
402
|
-
if (typeof window === 'undefined'
|
|
458
|
+
if (typeof window === 'undefined') {
|
|
403
459
|
return null;
|
|
404
460
|
}
|
|
405
461
|
|
|
406
|
-
const
|
|
462
|
+
const _window = window as WindowWithWallets;
|
|
463
|
+
if (!_window || !_window.opnet || !_window.opnet.web3) {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const opnet = _window.opnet.web3;
|
|
407
468
|
const interaction = await opnet.signInteraction({
|
|
408
469
|
...interactionParameters,
|
|
409
470
|
|
|
@@ -418,6 +479,33 @@ export class TransactionFactory {
|
|
|
418
479
|
return interaction;
|
|
419
480
|
}
|
|
420
481
|
|
|
482
|
+
private async detectDeploymentOPWallet(
|
|
483
|
+
deploymentParameters: IDeploymentParameters | IDeploymentParametersWithoutSigner,
|
|
484
|
+
): Promise<DeploymentResult | null> {
|
|
485
|
+
if (typeof window === 'undefined') {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const _window = window as WindowWithWallets;
|
|
490
|
+
if (!_window || !_window.opnet || !_window.opnet.web3) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const opnet = _window.opnet.web3;
|
|
495
|
+
const deployment = await opnet.deployContract({
|
|
496
|
+
...deploymentParameters,
|
|
497
|
+
|
|
498
|
+
// @ts-expect-error no, this is ok
|
|
499
|
+
signer: undefined,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
if (!deployment) {
|
|
503
|
+
throw new Error('Could not sign interaction transaction.');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return deployment;
|
|
507
|
+
}
|
|
508
|
+
|
|
421
509
|
private async _createChallengeSolution(
|
|
422
510
|
parameters: IChallengeSolutionTransactionParameters,
|
|
423
511
|
): Promise<ChallengeSolutionResponse> {
|
|
@@ -426,6 +514,7 @@ export class TransactionFactory {
|
|
|
426
514
|
const challengeTransaction: ChallengeSolutionTransaction = new ChallengeSolutionTransaction(
|
|
427
515
|
parameters,
|
|
428
516
|
);
|
|
517
|
+
|
|
429
518
|
const signedTransaction: Transaction = await challengeTransaction.signTransaction();
|
|
430
519
|
if (!signedTransaction) {
|
|
431
520
|
throw new Error('Could not sign funding transaction.');
|
|
@@ -14,11 +14,9 @@ import { canSignNonTaprootInput, isTaprootInput } from '../../../signer/SignerUt
|
|
|
14
14
|
import { CustomKeypair } from '../BrowserSignerBase.js';
|
|
15
15
|
import { PsbtSignatureOptions, SignatureType, Unisat, UnisatNetwork } from '../types/Unisat.js';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
opnet?: Unisat;
|
|
21
|
-
}
|
|
17
|
+
export interface WindowWithWallets {
|
|
18
|
+
unisat?: Unisat;
|
|
19
|
+
opnet?: Unisat;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
export class UnisatSigner extends CustomKeypair {
|
|
@@ -83,7 +81,9 @@ export class UnisatSigner extends CustomKeypair {
|
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
public get unisat(): Unisat {
|
|
86
|
-
|
|
84
|
+
if (!window) throw new Error('Window not found');
|
|
85
|
+
|
|
86
|
+
const module = (window as WindowWithWallets).unisat;
|
|
87
87
|
if (!module) {
|
|
88
88
|
throw new Error('Unisat extension not found');
|
|
89
89
|
}
|
|
@@ -118,7 +118,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
118
118
|
this.verifyCalldata();
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
if(!parameters.preimage) throw new Error('Preimage is required');
|
|
121
|
+
if (!parameters.preimage) throw new Error('Preimage is required');
|
|
122
122
|
|
|
123
123
|
this.randomBytes = parameters.randomBytes || BitcoinUtils.rndBytes();
|
|
124
124
|
this.preimage = parameters.preimage;
|
|
@@ -274,7 +274,11 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
274
274
|
if (i === 0) {
|
|
275
275
|
transaction.finalizeInput(i, this.customFinalizer);
|
|
276
276
|
} else {
|
|
277
|
-
|
|
277
|
+
try {
|
|
278
|
+
transaction.finalizeInput(i, this.customFinalizerP2SH);
|
|
279
|
+
} catch (e) {
|
|
280
|
+
transaction.finalizeInput(i);
|
|
281
|
+
}
|
|
278
282
|
}
|
|
279
283
|
}
|
|
280
284
|
}
|
|
@@ -305,7 +309,12 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
305
309
|
transaction.finalizeInput(0, this.customFinalizer);
|
|
306
310
|
} else {
|
|
307
311
|
transaction.signInput(i, this.getSignerKey());
|
|
308
|
-
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
transaction.finalizeInput(i, this.customFinalizerP2SH);
|
|
315
|
+
} catch (e) {
|
|
316
|
+
transaction.finalizeInput(i);
|
|
317
|
+
}
|
|
309
318
|
}
|
|
310
319
|
}
|
|
311
320
|
}
|
|
@@ -67,7 +67,7 @@ export abstract class SharedInteractionTransaction<
|
|
|
67
67
|
throw new Error('Calldata is required');
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
if(!parameters.preimage) {
|
|
70
|
+
if (!parameters.preimage) {
|
|
71
71
|
throw new Error('Preimage is required');
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -319,7 +319,36 @@ export abstract class SharedInteractionTransaction<
|
|
|
319
319
|
if (i === 0) {
|
|
320
320
|
transaction.finalizeInput(i, this.customFinalizer);
|
|
321
321
|
} else {
|
|
322
|
-
|
|
322
|
+
try {
|
|
323
|
+
transaction.finalizeInput(i, this.customFinalizerP2SH);
|
|
324
|
+
} catch (e) {
|
|
325
|
+
transaction.finalizeInput(i);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
protected override async signInputsNonWalletBased(transaction: Psbt): Promise<void> {
|
|
332
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
333
|
+
if (i === 0) {
|
|
334
|
+
await this.signInput(transaction, transaction.data.inputs[i], i, this.scriptSigner);
|
|
335
|
+
|
|
336
|
+
await this.signInput(
|
|
337
|
+
transaction,
|
|
338
|
+
transaction.data.inputs[i],
|
|
339
|
+
i,
|
|
340
|
+
this.getSignerKey(),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
transaction.finalizeInput(0, this.customFinalizer);
|
|
344
|
+
} else {
|
|
345
|
+
await this.signInput(transaction, transaction.data.inputs[i], i, this.signer);
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
transaction.finalizeInput(i, this.customFinalizerP2SH);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
transaction.finalizeInput(i);
|
|
351
|
+
}
|
|
323
352
|
}
|
|
324
353
|
}
|
|
325
354
|
}
|
|
@@ -359,32 +388,6 @@ export abstract class SharedInteractionTransaction<
|
|
|
359
388
|
}
|
|
360
389
|
}
|
|
361
390
|
|
|
362
|
-
private async signInputsNonWalletBased(transaction: Psbt): Promise<void> {
|
|
363
|
-
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
364
|
-
if (i === 0) {
|
|
365
|
-
await this.signInput(transaction, transaction.data.inputs[i], i, this.scriptSigner);
|
|
366
|
-
|
|
367
|
-
await this.signInput(
|
|
368
|
-
transaction,
|
|
369
|
-
transaction.data.inputs[i],
|
|
370
|
-
i,
|
|
371
|
-
this.getSignerKey(),
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
transaction.finalizeInput(i, this.customFinalizer);
|
|
375
|
-
} else {
|
|
376
|
-
await this.signInput(
|
|
377
|
-
transaction,
|
|
378
|
-
transaction.data.inputs[i],
|
|
379
|
-
i,
|
|
380
|
-
this.getSignerKey(),
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
transaction.finalizeInput(i);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
391
|
/**
|
|
389
392
|
* Get the public keys
|
|
390
393
|
* @private
|
|
@@ -626,9 +626,13 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
626
626
|
}
|
|
627
627
|
|
|
628
628
|
if (this.optionalInputs) {
|
|
629
|
-
for (
|
|
630
|
-
|
|
631
|
-
|
|
629
|
+
for (
|
|
630
|
+
let i = this.utxos.length;
|
|
631
|
+
i < this.optionalInputs.length + this.utxos.length;
|
|
632
|
+
i++
|
|
633
|
+
) {
|
|
634
|
+
const utxo = this.optionalInputs[i - this.utxos.length];
|
|
635
|
+
const input = this.generatePsbtInputExtended(utxo, i, true);
|
|
632
636
|
|
|
633
637
|
this.addInput(input);
|
|
634
638
|
}
|
|
@@ -462,6 +462,10 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
462
462
|
return;
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
+
await this.signInputsNonWalletBased(transaction);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
protected async signInputsNonWalletBased(transaction: Psbt): Promise<void> {
|
|
465
469
|
// non web based signing.
|
|
466
470
|
const txs: PsbtInput[] = transaction.data.inputs;
|
|
467
471
|
|
|
@@ -646,10 +650,15 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
646
650
|
* Generate the PSBT input extended, supporting various script types
|
|
647
651
|
* @param {UTXO} utxo The UTXO
|
|
648
652
|
* @param {number} i The index of the input
|
|
653
|
+
* @param {UTXO} extra Extra UTXO
|
|
649
654
|
* @protected
|
|
650
655
|
* @returns {PsbtInputExtended} The PSBT input extended
|
|
651
656
|
*/
|
|
652
|
-
protected generatePsbtInputExtended(
|
|
657
|
+
protected generatePsbtInputExtended(
|
|
658
|
+
utxo: UTXO,
|
|
659
|
+
i: number,
|
|
660
|
+
extra: boolean = false,
|
|
661
|
+
): PsbtInputExtended {
|
|
653
662
|
const script = Buffer.from(utxo.scriptPubKey.hex, 'hex');
|
|
654
663
|
|
|
655
664
|
const input: PsbtInputExtended = {
|
|
@@ -788,16 +797,29 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
788
797
|
this.error(`Unknown or unsupported script type for output: ${utxo.scriptPubKey.hex}`);
|
|
789
798
|
}
|
|
790
799
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
800
|
+
if (i === 0) {
|
|
801
|
+
// TapLeafScript if available
|
|
802
|
+
if (this.tapLeafScript) {
|
|
803
|
+
input.tapLeafScript = [this.tapLeafScript];
|
|
804
|
+
}
|
|
795
805
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
806
|
+
if (this.nonWitnessUtxo) {
|
|
807
|
+
input.nonWitnessUtxo = this.nonWitnessUtxo;
|
|
808
|
+
}
|
|
799
809
|
}
|
|
800
810
|
|
|
811
|
+
/*if (utxo.nonWitnessUtxo && extra) {
|
|
812
|
+
const witness = Buffer.isBuffer(utxo.nonWitnessUtxo)
|
|
813
|
+
? utxo.nonWitnessUtxo
|
|
814
|
+
: typeof utxo.nonWitnessUtxo === 'string'
|
|
815
|
+
? Buffer.from(utxo.nonWitnessUtxo, 'hex')
|
|
816
|
+
: (utxo.nonWitnessUtxo as unknown) instanceof Uint8Array
|
|
817
|
+
? Buffer.from(utxo.nonWitnessUtxo)
|
|
818
|
+
: undefined;
|
|
819
|
+
|
|
820
|
+
input.nonWitnessUtxo = witness;
|
|
821
|
+
}*/
|
|
822
|
+
|
|
801
823
|
return input;
|
|
802
824
|
}
|
|
803
825
|
|
|
@@ -861,7 +883,16 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
861
883
|
}
|
|
862
884
|
|
|
863
885
|
if (tweakedSigner) {
|
|
864
|
-
|
|
886
|
+
try {
|
|
887
|
+
await this.signTaprootInput(tweakedSigner, transaction, i);
|
|
888
|
+
} catch (e) {
|
|
889
|
+
tweakedSigner = this.getTweakedSigner(false, this.signer);
|
|
890
|
+
if (!tweakedSigner) {
|
|
891
|
+
throw new Error(`Failed to obtain tweaked signer for input ${i}.`);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
await this.signTaprootInput(tweakedSigner, transaction, i);
|
|
895
|
+
}
|
|
865
896
|
} else {
|
|
866
897
|
this.error(`Failed to obtain tweaked signer for input ${i}.`);
|
|
867
898
|
}
|