From ac4fbe73ba3ed12f73b43ae43927b3eeb8f9c436 Mon Sep 17 00:00:00 2001 From: Ravi Hegde Date: Tue, 28 Oct 2025 00:33:56 +0530 Subject: [PATCH] feat: added transfer reject builder Ticket: COIN-6019 --- modules/sdk-coin-canton/src/lib/iface.ts | 2 +- modules/sdk-coin-canton/src/lib/index.ts | 1 + .../src/lib/transactionBuilderFactory.ts | 8 ++ .../src/lib/transferAcceptanceBuilder.ts | 14 +- .../src/lib/transferRejectionBuilder.ts | 123 ++++++++++++++++++ modules/sdk-coin-canton/test/resources.ts | 15 +++ .../transferAccept/transferAcceptBuilder.ts | 18 +-- .../transferReject/transferRejectBuilder.ts | 57 ++++++++ .../sdk-core/src/account-lib/baseCoin/enum.ts | 2 + 9 files changed, 216 insertions(+), 24 deletions(-) create mode 100644 modules/sdk-coin-canton/src/lib/transferRejectionBuilder.ts create mode 100644 modules/sdk-coin-canton/test/unit/builder/transferReject/transferRejectBuilder.ts diff --git a/modules/sdk-coin-canton/src/lib/iface.ts b/modules/sdk-coin-canton/src/lib/iface.ts index f54dce6a49..ffe4a376c3 100644 --- a/modules/sdk-coin-canton/src/lib/iface.ts +++ b/modules/sdk-coin-canton/src/lib/iface.ts @@ -111,7 +111,7 @@ export interface CantonOneStepEnablementRequest extends CantonPrepareCommandRequ receiverId: string; } -export interface CantonTransferAcceptRequest extends CantonPrepareCommandRequest { +export interface CantonTransferAcceptRejectRequest extends CantonPrepareCommandRequest { contractId: string; } diff --git a/modules/sdk-coin-canton/src/lib/index.ts b/modules/sdk-coin-canton/src/lib/index.ts index bfff8ef769..d02de4c9e3 100644 --- a/modules/sdk-coin-canton/src/lib/index.ts +++ b/modules/sdk-coin-canton/src/lib/index.ts @@ -8,6 +8,7 @@ export { TransferAcceptanceBuilder } from './transferAcceptanceBuilder'; export { TransferAcknowledgeBuilder } from './transferAcknowledgeBuilder'; export { TransactionBuilder } from './transactionBuilder'; export { TransactionBuilderFactory } from './transactionBuilderFactory'; +export { TransferRejectionBuilder } from './transferRejectionBuilder'; export { WalletInitBuilder } from './walletInitBuilder'; export { WalletInitTransaction } from './walletInitialization/walletInitTransaction'; diff --git a/modules/sdk-coin-canton/src/lib/transactionBuilderFactory.ts b/modules/sdk-coin-canton/src/lib/transactionBuilderFactory.ts index 0418c8ea01..97e6a7c5a4 100644 --- a/modules/sdk-coin-canton/src/lib/transactionBuilderFactory.ts +++ b/modules/sdk-coin-canton/src/lib/transactionBuilderFactory.ts @@ -9,6 +9,7 @@ import { TransferAcceptanceBuilder } from './transferAcceptanceBuilder'; import { TransferAcknowledgeBuilder } from './transferAcknowledgeBuilder'; import { TransactionBuilder } from './transactionBuilder'; import { TransferBuilder } from './transferBuilder'; +import { TransferRejectionBuilder } from './transferRejectionBuilder'; import { Transaction } from './transaction/transaction'; import { WalletInitBuilder } from './walletInitBuilder'; import { WalletInitTransaction } from './walletInitialization/walletInitTransaction'; @@ -36,6 +37,9 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { case TransactionType.TransferAcknowledge: { return this.getTransferAcknowledgeBuilder(tx); } + case TransactionType.TransferReject: { + return this.getTransferRejectBuilder(tx); + } default: { throw new InvalidTransactionError('unsupported transaction'); } @@ -51,6 +55,10 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { return TransactionBuilderFactory.initializeBuilder(tx, new TransferAcknowledgeBuilder(this._coinConfig)); } + getTransferRejectBuilder(tx?: Transaction): TransferRejectionBuilder { + return TransactionBuilderFactory.initializeBuilder(tx, new TransferRejectionBuilder(this._coinConfig)); + } + /** @inheritdoc */ getTransferBuilder(tx?: Transaction): TransferBuilder { return TransactionBuilderFactory.initializeBuilder(tx, new TransferBuilder(this._coinConfig)); diff --git a/modules/sdk-coin-canton/src/lib/transferAcceptanceBuilder.ts b/modules/sdk-coin-canton/src/lib/transferAcceptanceBuilder.ts index 4a8319db2f..19d1e25f13 100644 --- a/modules/sdk-coin-canton/src/lib/transferAcceptanceBuilder.ts +++ b/modules/sdk-coin-canton/src/lib/transferAcceptanceBuilder.ts @@ -1,6 +1,6 @@ import { InvalidTransactionError, PublicKey, TransactionType } from '@bitgo/sdk-core'; import { BaseCoin as CoinConfig } from '@bitgo/statics'; -import { CantonPrepareCommandResponse, CantonTransferAcceptRequest } from './iface'; +import { CantonPrepareCommandResponse, CantonTransferAcceptRejectRequest } from './iface'; import { TransactionBuilder } from './transactionBuilder'; import { Transaction } from './transaction/transaction'; import utils from './utils'; @@ -50,7 +50,7 @@ export class TransferAcceptanceBuilder extends TransactionBuilder { * @throws Error if id is empty. */ commandId(id: string): this { - if (!id.trim()) { + if (!id || !id.trim()) { throw new Error('commandId must be a non-empty string'); } this._commandId = id.trim(); @@ -66,7 +66,7 @@ export class TransferAcceptanceBuilder extends TransactionBuilder { * @throws Error if id is empty. */ contractId(id: string): this { - if (!id.trim()) { + if (!id || !id.trim()) { throw new Error('contractId must be a non-empty string'); } this._contractId = id.trim(); @@ -81,7 +81,7 @@ export class TransferAcceptanceBuilder extends TransactionBuilder { * @throws Error if id is empty. */ actAs(id: string): this { - if (!id.trim()) { + if (!id || !id.trim()) { throw new Error('actAsPartyId must be a non-empty string'); } this._actAsPartyId = id.trim(); @@ -89,15 +89,15 @@ export class TransferAcceptanceBuilder extends TransactionBuilder { } /** - * Builds and returns the CantonTransferAcceptRequest object from the builder's internal state. + * Builds and returns the CantonTransferAcceptRejectRequest object from the builder's internal state. * * This method performs validation before constructing the object. If required fields are * missing or invalid, it throws an error. * - * @returns {CantonTransferAcceptRequest} - A fully constructed and validated request object for transfer acceptance. + * @returns {CantonTransferAcceptRejectRequest} - A fully constructed and validated request object for transfer acceptance. * @throws {Error} If any required field is missing or fails validation. */ - toRequestObject(): CantonTransferAcceptRequest { + toRequestObject(): CantonTransferAcceptRejectRequest { this.validate(); return { diff --git a/modules/sdk-coin-canton/src/lib/transferRejectionBuilder.ts b/modules/sdk-coin-canton/src/lib/transferRejectionBuilder.ts new file mode 100644 index 0000000000..94c134c5df --- /dev/null +++ b/modules/sdk-coin-canton/src/lib/transferRejectionBuilder.ts @@ -0,0 +1,123 @@ +import { InvalidTransactionError, PublicKey, TransactionType } from '@bitgo/sdk-core'; +import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import { CantonPrepareCommandResponse, CantonTransferAcceptRejectRequest } from './iface'; +import { TransactionBuilder } from './transactionBuilder'; +import { Transaction } from './transaction/transaction'; +import utils from './utils'; + +export class TransferRejectionBuilder extends TransactionBuilder { + private _commandId: string; + private _contractId: string; + private _actAsPartyId: string; + constructor(_coinConfig: Readonly) { + super(_coinConfig); + } + + initBuilder(tx: Transaction): void { + super.initBuilder(tx); + this.setTransactionType(); + } + + get transactionType(): TransactionType { + return TransactionType.TransferReject; + } + + setTransactionType(): void { + this.transaction.transactionType = TransactionType.TransferReject; + } + + setTransaction(transaction: CantonPrepareCommandResponse): void { + this.transaction.prepareCommand = transaction; + } + + /** @inheritDoc */ + addSignature(publicKey: PublicKey, signature: Buffer): void { + if (!this.transaction) { + throw new InvalidTransactionError('transaction is empty!'); + } + this._signatures.push({ publicKey, signature }); + const pubKeyBase64 = utils.getBase64FromHex(publicKey.pub); + this.transaction.signerFingerprint = utils.getAddressFromPublicKey(pubKeyBase64); + this.transaction.signatures = signature.toString('base64'); + } + + /** + * Sets the unique id for the transfer rejection + * Also sets the _id of the transaction + * + * @param id - A uuid + * @returns The current builder instance for chaining. + * @throws Error if id is empty. + */ + commandId(id: string): this { + if (!id || !id.trim()) { + throw new Error('commandId must be a non-empty string'); + } + this._commandId = id.trim(); + // also set the transaction _id + this.transaction.id = id.trim(); + return this; + } + + /** + * Sets the rejection contract id the receiver needs to accept + * @param id - canton rejection contract id + * @returns The current builder instance for chaining. + * @throws Error if id is empty. + */ + contractId(id: string): this { + if (!id || !id.trim()) { + throw new Error('contractId must be a non-empty string'); + } + this._contractId = id.trim(); + return this; + } + + /** + * Sets the receiver of the acceptance + * + * @param id - the receiver party id (address) + * @returns The current builder instance for chaining. + * @throws Error if id is empty. + */ + actAs(id: string): this { + if (!id || !id.trim()) { + throw new Error('actAsPartyId must be a non-empty string'); + } + this._actAsPartyId = id.trim(); + return this; + } + + /** + * Builds and returns the CantonTransferAcceptRejectRequest object from the builder's internal state. + * + * This method performs validation before constructing the object. If required fields are + * missing or invalid, it throws an error. + * + * @returns {CantonTransferAcceptRejectRequest} - A fully constructed and validated request object for transfer acceptance. + * @throws {Error} If any required field is missing or fails validation. + */ + toRequestObject(): CantonTransferAcceptRejectRequest { + this.validate(); + + return { + commandId: this._commandId, + contractId: this._contractId, + verboseHashing: false, + actAs: [this._actAsPartyId], + readAs: [], + }; + } + + /** + * Validates the internal state of the builder before building the request object. + * + * @private + * @throws {Error} If any required field is missing or invalid. + */ + private validate(): void { + if (!this._commandId) throw new Error('commandId is missing'); + if (!this._contractId) throw new Error('contractId is missing'); + if (!this._actAsPartyId) throw new Error('receiver partyId is missing'); + } +} diff --git a/modules/sdk-coin-canton/test/resources.ts b/modules/sdk-coin-canton/test/resources.ts index 0484b537fb..81fb3922a0 100644 --- a/modules/sdk-coin-canton/test/resources.ts +++ b/modules/sdk-coin-canton/test/resources.ts @@ -77,6 +77,13 @@ export const TransferAcceptance = { '001b549bfa833bab661ab30e4d0a3ab0ec01fcc4a2bef5369795f4928147706353ca1112205a8d0e780cf3b3115cf8be0d6315f4aed6a1c25b67e8c5d64cf9848d0458fd17', }; +export const TransferRejection = { + partyId: 'abcde::12205b4e3537a95126d90604592344d8ad3c3ddccda4f79901954280ee19c576714d', + commandId: '12eab71f-951f-4ee2-8fb4-495b04a680e4', + contractId: + '006d2cdb952b4d550951a48617e44d9355314b0b90a0e20e8b3f3f4ad09fb020c4ca111220f623b223d99fe1fbc3fc3503e044f1de576d93b26fdf12f0e491031d72cf4765', +}; + export const TransferAcknowledgeRequest = { contractId: '001b549bfa833bab661ab30e4d0a3ab0ec01fcc4a2bef5369795f4928147706353ca1112205a8d0e780cf3b3115cf8be0d6315f4aed6a1c25b67e8c5d64cf9848d0458fd17', @@ -93,3 +100,11 @@ export const TransferAcceptancePrepareResponse = { hashingSchemeVersion: 'HASHING_SCHEME_VERSION_V2', hashingDetails: null, }; + +export const TransferRejectionPrepareResponse = { + preparedTransaction: + 'Cps1CgMyLjESATAa7wMKATPCPugDEuUDCgMyLjESigEwMGQwMjI1Y2FiZGJjNDczODFlNzRhMzYzZmE5MjczZGUxOTE3MzhiYmNjOGRmNTAzZDI2YWU4YjRkNGEyOGFiNTVjYTExMTIyMDhjYzBkMTQ1NzJhZWIwMDZlMjYzNzc5M2U5NWQ4Y2MxZWQxZTVjOThlNWI3YzY0MWQ4NWNjYjlhZTMxNmY4NDcaDXNwbGljZS1hbXVsZXQiYQpAM2NhMTM0M2FiMjZiNDUzZDM4YzhhZGI3MGRjYTVmMWVhZDg0NDBjNDJiNTliNjhmMDcwNzg2OTU1Y2JmOWVjMRIMU3BsaWNlLlJvdW5kGg9PcGVuTWluaW5nUm91bmQqSURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWEySURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWE6SURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWEa7wMKATHCPugDEuUDCgMyLjESigEwMGQwMjI1Y2FiZGJjNDczODFlNzRhMzYzZmE5MjczZGUxOTE3MzhiYmNjOGRmNTAzZDI2YWU4YjRkNGEyOGFiNTVjYTExMTIyMDhjYzBkMTQ1NzJhZWIwMDZlMjYzNzc5M2U5NWQ4Y2MxZWQxZTVjOThlNWI3YzY0MWQ4NWNjYjlhZTMxNmY4NDcaDXNwbGljZS1hbXVsZXQiYQpAM2NhMTM0M2FiMjZiNDUzZDM4YzhhZGI3MGRjYTVmMWVhZDg0NDBjNDJiNTliNjhmMDcwNzg2OTU1Y2JmOWVjMRIMU3BsaWNlLlJvdW5kGg9PcGVuTWluaW5nUm91bmQqSURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWEySURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWE6SURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWEakwkKATTCPowJCokJCgMyLjESQjAwY2ZhMDZiOTk1Mzg2N2M1YzIzODE5NWMyNjU1NDZhODc2ZDBmMDgzMTBkY2NmMDkxYzY5NDE0ZjcyODlkYTY4ZRoNc3BsaWNlLWFtdWxldCJZCkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEg1TcGxpY2UuQW11bGV0GgZBbXVsZXQqowVyoAUKWQpAM2NhMTM0M2FiMjZiNDUzZDM4YzhhZGI3MGRjYTVmMWVhZDg0NDBjNDJiNTliNjhmMDcwNzg2OTU1Y2JmOWVjMRINU3BsaWNlLkFtdWxldBoGQW11bGV0ElIKA2RzbxJLOklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhElYKBW93bmVyEk06SzEyMjAxOjoxMjIwMzg0MDJjZjE2NTA4NzZkMjkyMGQ2MDQ3YjExYTRhYWYwZGU3YjQyOGU5OTE2MDA5Y2JhMmEyMmIxYWUyMmMxYRKWAwoGYW1vdW50EosDcogDCl8KQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESC1NwbGljZS5GZWVzGg5FeHBpcmluZ0Ftb3VudBIfCg1pbml0aWFsQW1vdW50Eg4yDDUuMDAwMDAwMDAwMBJ4CgljcmVhdGVkQXQSa3JpClcKQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESDFNwbGljZS5UeXBlcxoFUm91bmQSDgoGbnVtYmVyEgQY9IACEokBCgxyYXRlUGVyUm91bmQSeXJ3Cl0KQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESC1NwbGljZS5GZWVzGgxSYXRlUGVyUm91bmQSFgoEcmF0ZRIOMgwwLjAwMDE2OTExOTEySzEyMjAxOjoxMjIwMzg0MDJjZjE2NTA4NzZkMjkyMGQ2MDQ3YjExYTRhYWYwZGU3YjQyOGU5OTE2MDA5Y2JhMmEyMmIxYWUyMmMxYTJJRFNPOjoxMjIwYmU1OGMyOWU2NWRlNDBiZjI3M2JlMWRjMmIyNjZkNDNhOWEwMDJlYTViMTg5NTVhZWVmN2FhYzg4MWJiNDcxYTpLMTIyMDE6OjEyMjAzODQwMmNmMTY1MDg3NmQyOTIwZDYwNDdiMTFhNGFhZjBkZTdiNDI4ZTk5MTYwMDljYmEyYTIyYjFhZTIyYzFhOklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhGtwNCgEywj7VDRrSDQoDMi4xEooBMDAwYjViZDU2N2ZjYTA1NTlhYWI0NGZkZmEwNjk0NDZkMTVjN2E0YjQwMWNmYzc5YzkxNWM1MzU0ZGE1MzRiNjU4Y2ExMTEyMjA4NTAzYWQwYTlmNDg3YjMzN2I5ZWExZjdhNmRiZDRiOTJhNmZjYmNhZjBiYTVlOWFmY2M4NjIzZjM0MzJjYWU4Gg1zcGxpY2UtYW11bGV0Il8KQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESDVNwbGljZS5BbXVsZXQaDExvY2tlZEFtdWxldCpLMTIyMDE6OjEyMjAzODQwMmNmMTY1MDg3NmQyOTIwZDYwNDdiMTFhNGFhZjBkZTdiNDI4ZTk5MTYwMDljYmEyYTIyYjFhZTIyYzFhKklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhMksxMjIwMTo6MTIyMDM4NDAyY2YxNjUwODc2ZDI5MjBkNjA0N2IxMWE0YWFmMGRlN2I0MjhlOTkxNjAwOWNiYTJhMjJiMWFlMjJjMWEySURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWE6SzEyMjAxOjoxMjIwMzg0MDJjZjE2NTA4NzZkMjkyMGQ2MDQ3YjExYTRhYWYwZGU3YjQyOGU5OTE2MDA5Y2JhMmEyMmIxYWUyMmMxYTpJRFNPOjoxMjIwYmU1OGMyOWU2NWRlNDBiZjI3M2JlMWRjMmIyNjZkNDNhOWEwMDJlYTViMTg5NTVhZWVmN2FhYzg4MWJiNDcxYUoTTG9ja2VkQW11bGV0X1VubG9ja1KMAnKJAgpmCkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEg1TcGxpY2UuQW11bGV0GhNMb2NrZWRBbXVsZXRfVW5sb2NrEp4BCgxvcGVuUm91bmRDaWQSjQFKigEwMGQwMjI1Y2FiZGJjNDczODFlNzRhMzYzZmE5MjczZGUxOTE3MzhiYmNjOGRmNTAzZDI2YWU4YjRkNGEyOGFiNTVjYTExMTIyMDhjYzBkMTQ1NzJhZWIwMDZlMjYzNzc5M2U5NWQ4Y2MxZWQxZTVjOThlNWI3YzY0MWQ4NWNjYjlhZTMxNmY4NDdYAWIBM2IBNGrZBXLWBQpsCkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEg1TcGxpY2UuQW11bGV0GhlMb2NrZWRBbXVsZXRfVW5sb2NrUmVzdWx0Et4CCglhbXVsZXRTdW0S0AJyzQIKZgpAM2NhMTM0M2FiMjZiNDUzZDM4YzhhZGI3MGRjYTVmMWVhZDg0NDBjNDJiNTliNjhmMDcwNzg2OTU1Y2JmOWVjMRINU3BsaWNlLkFtdWxldBoTQW11bGV0Q3JlYXRlU3VtbWFyeRJOCgZhbXVsZXQSREpCMDBjZmEwNmI5OTUzODY3YzVjMjM4MTk1YzI2NTU0NmE4NzZkMGYwODMxMGRjY2YwOTFjNjk0MTRmNzI4OWRhNjhlEh0KC2FtdWxldFByaWNlEg4yDDAuMTEyNTAwMDAwMBJ0CgVyb3VuZBJrcmkKVwpAM2NhMTM0M2FiMjZiNDUzZDM4YzhhZGI3MGRjYTVmMWVhZDg0NDBjNDJiNTliNjhmMDcwNzg2OTU1Y2JmOWVjMRIMU3BsaWNlLlR5cGVzGgVSb3VuZBIOCgZudW1iZXISBBj4gAIShAIKBG1ldGES+wFS+AEK9QFy8gEKaQpANGRlZDZiNjY4Y2IzYjY0ZjdhODhhMzA4NzRjZDQxYzc1ODI5ZjVlMDY0YjNmYmJhZGY0MWVjN2U4MzYzMzU0ZhIbU3BsaWNlLkFwaS5Ub2tlbi5NZXRhZGF0YVYxGghNZXRhZGF0YRKEAQoGdmFsdWVzEnpieApBCiZzcGxpY2UubGZkZWNlbnRyYWxpemVkdHJ1c3Qub3JnL3JlYXNvbhIXQhVob2xkZXJzIHJlbGVhc2VkIGxvY2sKMwonc3BsaWNlLmxmZGVjZW50cmFsaXplZHRydXN0Lm9yZy90eC1raW5kEghCBnVubG9jaxrHFQoBMMI+wBUavRUKAzIuMRKKATAwNmQyY2RiOTUyYjRkNTUwOTUxYTQ4NjE3ZTQ0ZDkzNTUzMTRiMGI5MGEwZTIwZThiM2YzZjRhZDA5ZmIwMjBjNGNhMTExMjIwZjYyM2IyMjNkOTlmZTFmYmMzZmMzNTAzZTA0NGYxZGU1NzZkOTNiMjZmZGYxMmYwZTQ5MTAzMWQ3MmNmNDc2NRoNc3BsaWNlLWFtdWxldCJ/CkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEiBTcGxpY2UuQW11bGV0VHJhbnNmZXJJbnN0cnVjdGlvbhoZQW11bGV0VHJhbnNmZXJJbnN0cnVjdGlvbipLMTIyMDE6OjEyMjAzODQwMmNmMTY1MDg3NmQyOTIwZDYwNDdiMTFhNGFhZjBkZTdiNDI4ZTk5MTYwMDljYmEyYTIyYjFhZTIyYzFhKklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhMksxMjIwMTo6MTIyMDM4NDAyY2YxNjUwODc2ZDI5MjBkNjA0N2IxMWE0YWFmMGRlN2I0MjhlOTkxNjAwOWNiYTJhMjJiMWFlMjJjMWEySURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWEyS2FiY2RlOjoxMjIwNWI0ZTM1MzdhOTUxMjZkOTA2MDQ1OTIzNDRkOGFkM2MzZGRjY2RhNGY3OTkwMTk1NDI4MGVlMTljNTc2NzE0ZDpLYWJjZGU6OjEyMjA1YjRlMzUzN2E5NTEyNmQ5MDYwNDU5MjM0NGQ4YWQzYzNkZGNjZGE0Zjc5OTAxOTU0MjgwZWUxOWM1NzY3MTRkQn8KQDU1YmE0ZGViMGFkNDY2MmM0MTY4YjM5ODU5NzM4YTBlOTEzODhkMjUyMjg2NDgwYzczMzFiM2Y3MWE1MTcyODESJlNwbGljZS5BcGkuVG9rZW4uVHJhbnNmZXJJbnN0cnVjdGlvblYxGhNUcmFuc2Zlckluc3RydWN0aW9uShpUcmFuc2Zlckluc3RydWN0aW9uX1JlamVjdFLxCXLuCQqGAQpANTViYTRkZWIwYWQ0NjYyYzQxNjhiMzk4NTk3MzhhMGU5MTM4OGQyNTIyODY0ODBjNzMzMWIzZjcxYTUxNzI4MRImU3BsaWNlLkFwaS5Ub2tlbi5UcmFuc2Zlckluc3RydWN0aW9uVjEaGlRyYW5zZmVySW5zdHJ1Y3Rpb25fUmVqZWN0EuIICglleHRyYUFyZ3MS1Ahy0QgKagpANGRlZDZiNjY4Y2IzYjY0ZjdhODhhMzA4NzRjZDQxYzc1ODI5ZjVlMDY0YjNmYmJhZGY0MWVjN2U4MzYzMzU0ZhIbU3BsaWNlLkFwaS5Ub2tlbi5NZXRhZGF0YVYxGglFeHRyYUFyZ3MS3AYKB2NvbnRleHQS0AZyzQYKbgpANGRlZDZiNjY4Y2IzYjY0ZjdhODhhMzA4NzRjZDQxYzc1ODI5ZjVlMDY0YjNmYmJhZGY0MWVjN2U4MzYzMzU0ZhIbU3BsaWNlLkFwaS5Ub2tlbi5NZXRhZGF0YVYxGg1DaG9pY2VDb250ZXh0EtoFCgZ2YWx1ZXMSzwVizAUKngIKDGFtdWxldC1ydWxlcxKNAnqKAgppCkA0ZGVkNmI2NjhjYjNiNjRmN2E4OGEzMDg3NGNkNDFjNzU4MjlmNWUwNjRiM2ZiYmFkZjQxZWM3ZTgzNjMzNTRmEhtTcGxpY2UuQXBpLlRva2VuLk1ldGFkYXRhVjEaCEFueVZhbHVlEg1BVl9Db250cmFjdElkGo0BSooBMDA1NTRkZDQxZDQzNDM4ZDhlZTg4MzEyMDg1Yzc5NDQ3YTE2ZWZlNTVhN2ZjOTQxY2M0ZjY3YWYyZmUyNjhlZDczY2ExMTEyMjAxNmY3ZjllMWIwNTQ5MTIwZjk0MmY2M2MyNDljNTVjMjdmN2JkNGViZGUxYzg2OWNiOWQ3YjBlZGU1MDg0ZjZhCokBCgtleHBpcmUtbG9jaxJ6engKaQpANGRlZDZiNjY4Y2IzYjY0ZjdhODhhMzA4NzRjZDQxYzc1ODI5ZjVlMDY0YjNmYmJhZGY0MWVjN2U4MzYzMzU0ZhIbU3BsaWNlLkFwaS5Ub2tlbi5NZXRhZGF0YVYxGghBbnlWYWx1ZRIHQVZfQm9vbBoCEAEKnAIKCm9wZW4tcm91bmQSjQJ6igIKaQpANGRlZDZiNjY4Y2IzYjY0ZjdhODhhMzA4NzRjZDQxYzc1ODI5ZjVlMDY0YjNmYmJhZGY0MWVjN2U4MzYzMzU0ZhIbU3BsaWNlLkFwaS5Ub2tlbi5NZXRhZGF0YVYxGghBbnlWYWx1ZRINQVZfQ29udHJhY3RJZBqNAUqKATAwZDAyMjVjYWJkYmM0NzM4MWU3NGEzNjNmYTkyNzNkZTE5MTczOGJiY2M4ZGY1MDNkMjZhZThiNGQ0YTI4YWI1NWNhMTExMjIwOGNjMGQxNDU3MmFlYjAwNmUyNjM3NzkzZTk1ZDhjYzFlZDFlNWM5OGU1YjdjNjQxZDg1Y2NiOWFlMzE2Zjg0NxKDAQoEbWV0YRJ7cnkKaQpANGRlZDZiNjY4Y2IzYjY0ZjdhODhhMzA4NzRjZDQxYzc1ODI5ZjVlMDY0YjNmYmJhZGY0MWVjN2U4MzYzMzU0ZhIbU3BsaWNlLkFwaS5Ub2tlbi5NZXRhZGF0YVYxGghNZXRhZGF0YRIMCgZ2YWx1ZXMSAmIAWAFiATFiATJqtQRysgQKhQEKQDU1YmE0ZGViMGFkNDY2MmM0MTY4YjM5ODU5NzM4YTBlOTEzODhkMjUyMjg2NDgwYzczMzFiM2Y3MWE1MTcyODESJlNwbGljZS5BcGkuVG9rZW4uVHJhbnNmZXJJbnN0cnVjdGlvblYxGhlUcmFuc2Zlckluc3RydWN0aW9uUmVzdWx0EsMBCgZvdXRwdXQSuAF6tQEKjAEKQDU1YmE0ZGViMGFkNDY2MmM0MTY4YjM5ODU5NzM4YTBlOTEzODhkMjUyMjg2NDgwYzczMzFiM2Y3MWE1MTcyODESJlNwbGljZS5BcGkuVG9rZW4uVHJhbnNmZXJJbnN0cnVjdGlvblYxGiBUcmFuc2Zlckluc3RydWN0aW9uUmVzdWx0X091dHB1dBIgVHJhbnNmZXJJbnN0cnVjdGlvblJlc3VsdF9GYWlsZWQaAgoAElwKEHNlbmRlckNoYW5nZUNpZHMSSFpGCkRKQjAwY2ZhMDZiOTk1Mzg2N2M1YzIzODE5NWMyNjU1NDZhODc2ZDBmMDgzMTBkY2NmMDkxYzY5NDE0ZjcyODlkYTY4ZRKDAQoEbWV0YRJ7cnkKaQpANGRlZDZiNjY4Y2IzYjY0ZjdhODhhMzA4NzRjZDQxYzc1ODI5ZjVlMDY0YjNmYmJhZGY0MWVjN2U4MzYzMzU0ZhIbU3BsaWNlLkFwaS5Ub2tlbi5NZXRhZGF0YVYxGghNZXRhZGF0YRIMCgZ2YWx1ZXMSAmIAIiISICwtnuWC2g6JdWeAcZR6enYWmewlZIZeNdeq7sa1q4HGIiQIAhIg54JkXK5SKYaUQTVyTgkrX89y3d6iwEpuwxDcYCViacQiJAgEEiA5JdD1v/bsU9DyEMjlbC5kTX/jNH7gro6Syr8f/GOLhhLFSRJzCkthYmNkZTo6MTIyMDViNGUzNTM3YTk1MTI2ZDkwNjA0NTkyMzQ0ZDhhZDNjM2RkY2NkYTRmNzk5MDE5NTQyODBlZTE5YzU3NjcxNGQSJDEyZWFiNzFmLTk1MWYtNGVlMi04ZmI0LTQ5NWIwNGE2ODBlNBpTZ2xvYmFsLWRvbWFpbjo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWEqJGUwYzM1MTQ2LWU0MTctNDU3YS1hOWM0LThlZjZhOTlhOGY4YjCm6puahsWQAzrwEwqZDQoDMi4xEooBMDAwYjViZDU2N2ZjYTA1NTlhYWI0NGZkZmEwNjk0NDZkMTVjN2E0YjQwMWNmYzc5YzkxNWM1MzU0ZGE1MzRiNjU4Y2ExMTEyMjA4NTAzYWQwYTlmNDg3YjMzN2I5ZWExZjdhNmRiZDRiOTJhNmZjYmNhZjBiYTVlOWFmY2M4NjIzZjM0MzJjYWU4Gg1zcGxpY2UtYW11bGV0Il8KQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESDVNwbGljZS5BbXVsZXQaDExvY2tlZEFtdWxldCrkCHLhCApfCkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEg1TcGxpY2UuQW11bGV0GgxMb2NrZWRBbXVsZXQSrgUKBmFtdWxldBKjBXKgBQpZCkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEg1TcGxpY2UuQW11bGV0GgZBbXVsZXQSUgoDZHNvEks6SURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWESVgoFb3duZXISTTpLMTIyMDE6OjEyMjAzODQwMmNmMTY1MDg3NmQyOTIwZDYwNDdiMTFhNGFhZjBkZTdiNDI4ZTk5MTYwMDljYmEyYTIyYjFhZTIyYzFhEpYDCgZhbW91bnQSiwNyiAMKXwpAM2NhMTM0M2FiMjZiNDUzZDM4YzhhZGI3MGRjYTVmMWVhZDg0NDBjNDJiNTliNjhmMDcwNzg2OTU1Y2JmOWVjMRILU3BsaWNlLkZlZXMaDkV4cGlyaW5nQW1vdW50Eh8KDWluaXRpYWxBbW91bnQSDjIMNS4wMDAwMDAwMDAwEngKCWNyZWF0ZWRBdBJrcmkKVwpAM2NhMTM0M2FiMjZiNDUzZDM4YzhhZGI3MGRjYTVmMWVhZDg0NDBjNDJiNTliNjhmMDcwNzg2OTU1Y2JmOWVjMRIMU3BsaWNlLlR5cGVzGgVSb3VuZBIOCgZudW1iZXISBBj0gAISiQEKDHJhdGVQZXJSb3VuZBJ5cncKXQpAM2NhMTM0M2FiMjZiNDUzZDM4YzhhZGI3MGRjYTVmMWVhZDg0NDBjNDJiNTliNjhmMDcwNzg2OTU1Y2JmOWVjMRILU3BsaWNlLkZlZXMaDFJhdGVQZXJSb3VuZBIWCgRyYXRlEg4yDDAuMDAwMTY5MTE5MRLMAgoEbG9jaxLDAnLAAgpbCkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEg1TcGxpY2UuRXhwaXJ5GghUaW1lTG9jaxJaCgdob2xkZXJzEk9aTQpLOklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhEhYKCWV4cGlyZXNBdBIJKSAnPJ+DRAYAEm0KCm9wdENvbnRleHQSX1JdCltCWXRyYW5zZmVyIHRvICdhYmNkZTo6MTIyMDViNGUzNTM3YTk1MTI2ZDkwNjA0NTkyMzQ0ZDhhZDNjM2RkY2NkYTRmNzk5MDE5NTQyODBlZTE5YzU3NjcxNGQnMksxMjIwMTo6MTIyMDM4NDAyY2YxNjUwODc2ZDI5MjBkNjA0N2IxMWE0YWFmMGRlN2I0MjhlOTkxNjAwOWNiYTJhMjJiMWFlMjJjMWEySURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWE6SzEyMjAxOjoxMjIwMzg0MDJjZjE2NTA4NzZkMjkyMGQ2MDQ3YjExYTRhYWYwZGU3YjQyOGU5OTE2MDA5Y2JhMmEyMmIxYWUyMmMxYTpJRFNPOjoxMjIwYmU1OGMyOWU2NWRlNDBiZjI3M2JlMWRjMmIyNjZkNDNhOWEwMDJlYTViMTg5NTVhZWVmN2FhYzg4MWJiNDcxYcA+7puHgoLFkAPSPsYGCgMyLjESvgYKRQALW9Vn/KBVmqtE/foGlEbRXHpLQBz8eckVxTVNpTS2WMoREiCFA60Kn0h7M3ueofem29S5Km/LyvC6Xpr8yGI/NDLK6BINc3BsaWNlLWFtdWxldBpgCkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEgZTcGxpY2USBkFtdWxldBoMTG9ja2VkQW11bGV0IrYDarMDCuQBCuEBat4BCk0KSzpJRFNPOjoxMjIwYmU1OGMyOWU2NWRlNDBiZjI3M2JlMWRjMmIyNjZkNDNhOWEwMDJlYTViMTg5NTVhZWVmN2FhYzg4MWJiNDcxYQpPCk06SzEyMjAxOjoxMjIwMzg0MDJjZjE2NTA4NzZkMjkyMGQ2MDQ3YjExYTRhYWYwZGU3YjQyOGU5OTE2MDA5Y2JhMmEyMmIxYWUyMmMxYQo8CjpqOAoQCg4yDDUuMDAwMDAwMDAwMAoMCgpqCAoGCgQY9IACChYKFGoSChAKDjIMMC4wMDAxNjkxMTkxCskBCsYBasMBClEKT1pNCks6SURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWEKCwoJKSAnPJ+DRAYACmEKX1JdCltCWXRyYW5zZmVyIHRvICdhYmNkZTo6MTIyMDViNGUzNTM3YTk1MTI2ZDkwNjA0NTkyMzQ0ZDhhZDNjM2RkY2NkYTRmNzk5MDE5NTQyODBlZTE5YzU3NjcxNGQnKksxMjIwMTo6MTIyMDM4NDAyY2YxNjUwODc2ZDI5MjBkNjA0N2IxMWE0YWFmMGRlN2I0MjhlOTkxNjAwOWNiYTJhMjJiMWFlMjJjMWEqSURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWE57s1BIChCBgBCKgomCiQIARIgQMQjhp0GeBhK1UttYNI48Lki6mqZ91GUrkNIgakXduIQHjrhFgrwDgoDMi4xEooBMDA2ZDJjZGI5NTJiNGQ1NTA5NTFhNDg2MTdlNDRkOTM1NTMxNGIwYjkwYTBlMjBlOGIzZjNmNGFkMDlmYjAyMGM0Y2ExMTEyMjBmNjIzYjIyM2Q5OWZlMWZiYzNmYzM1MDNlMDQ0ZjFkZTU3NmQ5M2IyNmZkZjEyZjBlNDkxMDMxZDcyY2Y0NzY1Gg1zcGxpY2UtYW11bGV0In8KQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESIFNwbGljZS5BbXVsZXRUcmFuc2Zlckluc3RydWN0aW9uGhlBbXVsZXRUcmFuc2Zlckluc3RydWN0aW9uKs4JcssJCn8KQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESIFNwbGljZS5BbXVsZXRUcmFuc2Zlckluc3RydWN0aW9uGhlBbXVsZXRUcmFuc2Zlckluc3RydWN0aW9uEp4BCgxsb2NrZWRBbXVsZXQSjQFKigEwMDBiNWJkNTY3ZmNhMDU1OWFhYjQ0ZmRmYTA2OTQ0NmQxNWM3YTRiNDAxY2ZjNzljOTE1YzUzNTRkYTUzNGI2NThjYTExMTIyMDg1MDNhZDBhOWY0ODdiMzM3YjllYTFmN2E2ZGJkNGI5MmE2ZmNiY2FmMGJhNWU5YWZjYzg2MjNmMzQzMmNhZTgSpgcKCHRyYW5zZmVyEpkHcpYHCnQKQDU1YmE0ZGViMGFkNDY2MmM0MTY4YjM5ODU5NzM4YTBlOTEzODhkMjUyMjg2NDgwYzczMzFiM2Y3MWE1MTcyODESJlNwbGljZS5BcGkuVG9rZW4uVHJhbnNmZXJJbnN0cnVjdGlvblYxGghUcmFuc2ZlchJXCgZzZW5kZXISTTpLMTIyMDE6OjEyMjAzODQwMmNmMTY1MDg3NmQyOTIwZDYwNDdiMTFhNGFhZjBkZTdiNDI4ZTk5MTYwMDljYmEyYTIyYjFhZTIyYzFhElkKCHJlY2VpdmVyEk06S2FiY2RlOjoxMjIwNWI0ZTM1MzdhOTUxMjZkOTA2MDQ1OTIzNDRkOGFkM2MzZGRjY2RhNGY3OTkwMTk1NDI4MGVlMTljNTc2NzE0ZBIYCgZhbW91bnQSDjIMNS4wMDAwMDAwMDAwEugBCgxpbnN0cnVtZW50SWQS1wFy1AEKbApANzE4YTBmNzdlNTA1YThkZTIyZjE4OGJkNGM4N2ZlNzQxMDEyNzRlOWQ0Y2IxYmZhYzdkMDlhZWM3MTU4ZDM1YhIaU3BsaWNlLkFwaS5Ub2tlbi5Ib2xkaW5nVjEaDEluc3RydW1lbnRJZBJUCgVhZG1pbhJLOklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhEg4KAmlkEghCBkFtdWxldBIYCgtyZXF1ZXN0ZWRBdBIJKSDn/h8oQgYAEhoKDWV4ZWN1dGVCZWZvcmUSCSkgJzyfg0QGABKoAQoQaW5wdXRIb2xkaW5nQ2lkcxKTAVqQAQqNAUqKATAwMGI1YmQ1NjdmY2EwNTU5YWFiNDRmZGZhMDY5NDQ2ZDE1YzdhNGI0MDFjZmM3OWM5MTVjNTM1NGRhNTM0YjY1OGNhMTExMjIwODUwM2FkMGE5ZjQ4N2IzMzdiOWVhMWY3YTZkYmQ0YjkyYTZmY2JjYWYwYmE1ZTlhZmNjODYyM2YzNDMyY2FlOBKDAQoEbWV0YRJ7cnkKaQpANGRlZDZiNjY4Y2IzYjY0ZjdhODhhMzA4NzRjZDQxYzc1ODI5ZjVlMDY0YjNmYmJhZGY0MWVjN2U4MzYzMzU0ZhIbU3BsaWNlLkFwaS5Ub2tlbi5NZXRhZGF0YVYxGghNZXRhZGF0YRIMCgZ2YWx1ZXMSAmIAMksxMjIwMTo6MTIyMDM4NDAyY2YxNjUwODc2ZDI5MjBkNjA0N2IxMWE0YWFmMGRlN2I0MjhlOTkxNjAwOWNiYTJhMjJiMWFlMjJjMWEySURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWE6SzEyMjAxOjoxMjIwMzg0MDJjZjE2NTA4NzZkMjkyMGQ2MDQ3YjExYTRhYWYwZGU3YjQyOGU5OTE2MDA5Y2JhMmEyMmIxYWUyMmMxYTpJRFNPOjoxMjIwYmU1OGMyOWU2NWRlNDBiZjI3M2JlMWRjMmIyNjZkNDNhOWEwMDJlYTViMTg5NTVhZWVmN2FhYzg4MWJiNDcxYTpLYWJjZGU6OjEyMjA1YjRlMzUzN2E5NTEyNmQ5MDYwNDU5MjM0NGQ4YWQzYzNkZGNjZGE0Zjc5OTAxOTU0MjgwZWUxOWM1NzY3MTRkwD7um4eCgsWQA9I+4AcKAzIuMRLYBwpFAG0s25UrTVUJUaSGF+RNk1UxSwuQoOIOiz8/StCfsCDEyhESIPYjsiPZn+H7w/w1A+BE8d5XbZOyb98S8OSRAx1yz0dlEg1zcGxpY2UtYW11bGV0GoABCkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEgZTcGxpY2USGUFtdWxldFRyYW5zZmVySW5zdHJ1Y3Rpb24aGUFtdWxldFRyYW5zZmVySW5zdHJ1Y3Rpb24i4gNq3wMKSQpHSkUAC1vVZ/ygVZqrRP36BpRG0Vx6S0Ac/HnJFcU1TaU0tljKERIghQOtCp9IezN7nqH3ptvUuSpvy8rwul6a/MhiPzQyyugKkQMKjgNqiwMKTwpNOksxMjIwMTo6MTIyMDM4NDAyY2YxNjUwODc2ZDI5MjBkNjA0N2IxMWE0YWFmMGRlN2I0MjhlOTkxNjAwOWNiYTJhMjJiMWFlMjJjMWEKTwpNOkthYmNkZTo6MTIyMDViNGUzNTM3YTk1MTI2ZDkwNjA0NTkyMzQ0ZDhhZDNjM2RkY2NkYTRmNzk5MDE5NTQyODBlZTE5YzU3NjcxNGQKEAoOMgw1LjAwMDAwMDAwMDAKXwpdalsKTQpLOklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhCgoKCEIGQW11bGV0CgsKCSkg5/4fKEIGAAoLCgkpICc8n4NEBgAKTQpLWkkKR0pFAAtb1Wf8oFWaq0T9+gaURtFcektAHPx5yRXFNU2lNLZYyhESIIUDrQqfSHsze56h96bb1Lkqb8vK8LpemvzIYj80MsroCgsKCWoHCgUKA4IBACpLMTIyMDE6OjEyMjAzODQwMmNmMTY1MDg3NmQyOTIwZDYwNDdiMTFhNGFhZjBkZTdiNDI4ZTk5MTYwMDljYmEyYTIyYjFhZTIyYzFhKklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhMkthYmNkZTo6MTIyMDViNGUzNTM3YTk1MTI2ZDkwNjA0NTkyMzQ0ZDhhZDNjM2RkY2NkYTRmNzk5MDE5NTQyODBlZTE5YzU3NjcxNGQ57s1BIChCBgBCKgomCiQIARIgBjptr8zVbHHZNOjoNhWkMQIMTYidlwnTGcIxnTdKK9cQHjryHAq1FQoDMi4xEooBMDBkMDIyNWNhYmRiYzQ3MzgxZTc0YTM2M2ZhOTI3M2RlMTkxNzM4YmJjYzhkZjUwM2QyNmFlOGI0ZDRhMjhhYjU1Y2ExMTEyMjA4Y2MwZDE0NTcyYWViMDA2ZTI2Mzc3OTNlOTVkOGNjMWVkMWU1Yzk4ZTViN2M2NDFkODVjY2I5YWUzMTZmODQ3Gg1zcGxpY2UtYW11bGV0ImEKQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESDFNwbGljZS5Sb3VuZBoPT3Blbk1pbmluZ1JvdW5kKpgScpUSCmEKQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESDFNwbGljZS5Sb3VuZBoPT3Blbk1pbmluZ1JvdW5kElIKA2RzbxJLOklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhEnQKBXJvdW5kEmtyaQpXCkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEgxTcGxpY2UuVHlwZXMaBVJvdW5kEg4KBm51bWJlchIEGPiAAhIdCgthbXVsZXRQcmljZRIOMgwwLjExMjUwMDAwMDASFAoHb3BlbnNBdBIJKVZ8l00oQgYAEhsKDnRhcmdldENsb3Nlc0F0EgkpVggelShCBgAShgEKCmlzc3VpbmdGb3ISeHJ2CloKQGI3MGRiODM2OWUxYzQ2MWQ1YzcwZjFjODZmNTI2YTI5ZTk3NzZjNjU1ZTZmZmMyNTYwZjk1YjA1Y2NiOGI5NDYSDURBLlRpbWUuVHlwZXMaB1JlbFRpbWUSGAoMbWljcm9zZWNvbmRzEggYgKCjtKa+BBLSCQoRdHJhbnNmZXJDb25maWdVc2QSvAlyuQkKZwpAM2NhMTM0M2FiMjZiNDUzZDM4YzhhZGI3MGRjYTVmMWVhZDg0NDBjNDJiNTliNjhmMDcwNzg2OTU1Y2JmOWVjMRITU3BsaWNlLkFtdWxldENvbmZpZxoOVHJhbnNmZXJDb25maWcSgQEKCWNyZWF0ZUZlZRJ0cnIKWQpAM2NhMTM0M2FiMjZiNDUzZDM4YzhhZGI3MGRjYTVmMWVhZDg0NDBjNDJiNTliNjhmMDcwNzg2OTU1Y2JmOWVjMRILU3BsaWNlLkZlZXMaCEZpeGVkRmVlEhUKA2ZlZRIOMgwwLjAwMDAwMDAwMDAShwEKCmhvbGRpbmdGZWUSeXJ3Cl0KQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESC1NwbGljZS5GZWVzGgxSYXRlUGVyUm91bmQSFgoEcmF0ZRIOMgwwLjAwMDAxOTAyNTkSwwQKC3RyYW5zZmVyRmVlErMEcrAEClwKQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESC1NwbGljZS5GZWVzGgtTdGVwcGVkUmF0ZRIdCgtpbml0aWFsUmF0ZRIOMgwwLjAwMDAwMDAwMDASsAMKBXN0ZXBzEqYDWqMDCocBcoQBClQKQDVhZWU5YjIxYjhlOWE0YzQ5NzViNWY0YzQxOThlNmU2ZTg0NjlkZjQ5ZTIwMTA4MjBlNzkyZjM5M2RiODcwZjQSCERBLlR5cGVzGgZUdXBsZTISFgoCXzESEDIOMTAwLjAwMDAwMDAwMDASFAoCXzISDjIMMC4wMDAwMDAwMDAwCogBcoUBClQKQDVhZWU5YjIxYjhlOWE0YzQ5NzViNWY0YzQxOThlNmU2ZTg0NjlkZjQ5ZTIwMTA4MjBlNzkyZjM5M2RiODcwZjQSCERBLlR5cGVzGgZUdXBsZTISFwoCXzESETIPMTAwMC4wMDAwMDAwMDAwEhQKAl8yEg4yDDAuMDAwMDAwMDAwMAqLAXKIAQpUCkA1YWVlOWIyMWI4ZTlhNGM0OTc1YjVmNGM0MTk4ZTZlNmU4NDY5ZGY0OWUyMDEwODIwZTc5MmYzOTNkYjg3MGY0EghEQS5UeXBlcxoGVHVwbGUyEhoKAl8xEhQyEjEwMDAwMDAuMDAwMDAwMDAwMBIUCgJfMhIOMgwwLjAwMDAwMDAwMDAShQEKDWxvY2tIb2xkZXJGZWUSdHJyClkKQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESC1NwbGljZS5GZWVzGghGaXhlZEZlZRIVCgNmZWUSDjIMMC4wMDAwMDAwMDAwEi4KHGV4dHJhRmVhdHVyZWRBcHBSZXdhcmRBbW91bnQSDjIMMS4wMDAwMDAwMDAwEhMKDG1heE51bUlucHV0cxIDGMgBEhQKDW1heE51bU91dHB1dHMSAxjIARIXChFtYXhOdW1Mb2NrSG9sZGVycxICGGQSrAMKDmlzc3VhbmNlQ29uZmlnEpkDcpYDCmMKQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESD1NwbGljZS5Jc3N1YW5jZRoOSXNzdWFuY2VDb25maWcSMAoUYW11bGV0VG9Jc3N1ZVBlclllYXISGDIWNDAwMDAwMDAwMDAuMDAwMDAwMDAwMBIrChl2YWxpZGF0b3JSZXdhcmRQZXJjZW50YWdlEg4yDDAuMDUwMDAwMDAwMBIlChNhcHBSZXdhcmRQZXJjZW50YWdlEg4yDDAuMTUwMDAwMDAwMBIkChJ2YWxpZGF0b3JSZXdhcmRDYXASDjIMMC4yMDAwMDAwMDAwEioKFGZlYXR1cmVkQXBwUmV3YXJkQ2FwEhIyEDIwMDAwLjAwMDAwMDAwMDASKAoWdW5mZWF0dXJlZEFwcFJld2FyZENhcBIOMgwwLjYwMDAwMDAwMDASLQoVb3B0VmFsaWRhdG9yRmF1Y2V0Q2FwEhRSEgoQMg41NzAuMDAwMDAwMDAwMBKGAQoMdGlja0R1cmF0aW9uEnZydApaCkBiNzBkYjgzNjllMWM0NjFkNWM3MGYxYzg2ZjUyNmEyOWU5Nzc2YzY1NWU2ZmZjMjU2MGY5NWIwNWNjYjhiOTQ2Eg1EQS5UaW1lLlR5cGVzGgdSZWxUaW1lEhYKDG1pY3Jvc2Vjb25kcxIGGICYmrwEMklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhOklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhwD7W7NDOgsWQA9I+rAcKAzIuMRKkBwpFANAiXKvbxHOB50o2P6knPeGRc4u8yN9QPSaui01KKKtVyhESIIzA0UVyrrAG4mN3k+ldjMHtHlyY5bfGQdhcy5rjFvhHEg1zcGxpY2UtYW11bGV0GmIKQDNjYTEzNDNhYjI2YjQ1M2QzOGM4YWRiNzBkY2E1ZjFlYWQ4NDQwYzQyYjU5YjY4ZjA3MDc4Njk1NWNiZjllYzESBlNwbGljZRIFUm91bmQaD09wZW5NaW5pbmdSb3VuZCLnBGrkBApNCks6SURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWEKDAoKaggKBgoEGPiAAgoQCg4yDDAuMTEyNTAwMDAwMAoLCgkpVnyXTShCBgAKCwoJKVYIHpUoQgYAChAKDmoMCgoKCBiAoKO0pr4ECpsCCpgCapUCChYKFGoSChAKDjIMMC4wMDAwMDAwMDAwChYKFGoSChAKDjIMMC4wMDAwMTkwMjU5CqQBCqEBap4BChAKDjIMMC4wMDAwMDAwMDAwCokBCoYBWoMBCihqJgoSChAyDjEwMC4wMDAwMDAwMDAwChAKDjIMMC4wMDAwMDAwMDAwCilqJwoTChEyDzEwMDAuMDAwMDAwMDAwMAoQCg4yDDAuMDAwMDAwMDAwMAosaioKFgoUMhIxMDAwMDAwLjAwMDAwMDAwMDAKEAoOMgwwLjAwMDAwMDAwMDAKFgoUahIKEAoOMgwwLjAwMDAwMDAwMDAKEAoOMgwxLjAwMDAwMDAwMDAKBQoDGMgBCgUKAxjIAQoECgIYZAqYAQqVAWqSAQoaChgyFjQwMDAwMDAwMDAwLjAwMDAwMDAwMDAKEAoOMgwwLjA1MDAwMDAwMDAKEAoOMgwwLjE1MDAwMDAwMDAKEAoOMgwwLjIwMDAwMDAwMDAKFAoSMhAyMDAwMC4wMDAwMDAwMDAwChAKDjIMMC42MDAwMDAwMDAwChYKFFISChAyDjU3MC4wMDAwMDAwMDAwCg4KDGoKCggKBhiAmJq8BCpJRFNPOjoxMjIwYmU1OGMyOWU2NWRlNDBiZjI3M2JlMWRjMmIyNjZkNDNhOWEwMDJlYTViMTg5NTVhZWVmN2FhYzg4MWJiNDcxYTlWNtQpKEIGAEIqCiYKJAgBEiB70VjqNbnL7gLcikDfqiQZH3GMvSgFWG8HCUs9szLmiRAe', + preparedTransactionHash: 'QFxX1WBdq7lZbSc45iKA3J/oOF9mrVLc3DeKphAjb14=', + hashingSchemeVersion: 'HASHING_SCHEME_VERSION_V2', + hashingDetails: null, +}; diff --git a/modules/sdk-coin-canton/test/unit/builder/transferAccept/transferAcceptBuilder.ts b/modules/sdk-coin-canton/test/unit/builder/transferAccept/transferAcceptBuilder.ts index cdbdacc10e..df0876efce 100644 --- a/modules/sdk-coin-canton/test/unit/builder/transferAccept/transferAcceptBuilder.ts +++ b/modules/sdk-coin-canton/test/unit/builder/transferAccept/transferAcceptBuilder.ts @@ -4,7 +4,7 @@ import should from 'should'; import { coins } from '@bitgo/statics'; import { TransferAcceptanceBuilder, Transaction } from '../../../../src'; -import { CantonTransferAcceptRequest } from '../../../../src/lib/iface'; +import { CantonTransferAcceptRejectRequest } from '../../../../src/lib/iface'; import { TransferAcceptance, TransferAcceptancePrepareResponse } from '../../../resources'; @@ -15,7 +15,7 @@ describe('Transfer Acceptance Builder', () => { txBuilder.initBuilder(transferAcceptanceTx); const { commandId, contractId, partyId } = TransferAcceptance; txBuilder.commandId(commandId).contractId(contractId).actAs(partyId); - const requestObj: CantonTransferAcceptRequest = txBuilder.toRequestObject(); + const requestObj: CantonTransferAcceptRejectRequest = txBuilder.toRequestObject(); should.exist(requestObj); assert.equal(requestObj.commandId, commandId); assert.equal(requestObj.contractId, contractId); @@ -54,18 +54,4 @@ describe('Transfer Acceptance Builder', () => { assert.equal(e.message, 'invalid raw transaction, hash not matching'); } }); - - it('should throw error in validating raw transaction', function () { - const txBuilder = new TransferAcceptanceBuilder(coins.get('tcanton')); - const oneStepEnablementTx = new Transaction(coins.get('tcanton')); - txBuilder.initBuilder(oneStepEnablementTx); - const invalidPrepareResponse = TransferAcceptancePrepareResponse; - invalidPrepareResponse.preparedTransactionHash = '+vlIXv6Vgd2ypPXD0mrdn7RlcSH4c2hCRj2/tXqqUVs='; - oneStepEnablementTx.prepareCommand = invalidPrepareResponse; - try { - txBuilder.validateTransaction(oneStepEnablementTx); - } catch (e) { - assert.equal(e.message, 'invalid transaction'); - } - }); }); diff --git a/modules/sdk-coin-canton/test/unit/builder/transferReject/transferRejectBuilder.ts b/modules/sdk-coin-canton/test/unit/builder/transferReject/transferRejectBuilder.ts new file mode 100644 index 0000000000..bb3120e6f0 --- /dev/null +++ b/modules/sdk-coin-canton/test/unit/builder/transferReject/transferRejectBuilder.ts @@ -0,0 +1,57 @@ +import assert from 'assert'; +import should from 'should'; + +import { coins } from '@bitgo/statics'; + +import { Transaction, TransferRejectionBuilder } from '../../../../src'; +import { CantonTransferAcceptRejectRequest } from '../../../../src/lib/iface'; + +import { TransferRejection, TransferRejectionPrepareResponse } from '../../../resources'; + +describe('Transfer Rejection Builder', () => { + it('should get the transfer rejection request object', function () { + const txBuilder = new TransferRejectionBuilder(coins.get('tcanton')); + const tx = new Transaction(coins.get('tcanton')); + txBuilder.initBuilder(tx); + const { commandId, contractId, partyId } = TransferRejection; + txBuilder.commandId(commandId).contractId(contractId).actAs(partyId); + const requestObj: CantonTransferAcceptRejectRequest = txBuilder.toRequestObject(); + should.exist(requestObj); + assert.equal(requestObj.commandId, commandId); + assert.equal(requestObj.contractId, contractId); + assert.equal(requestObj.actAs.length, 1); + const actAs = requestObj.actAs[0]; + assert.equal(actAs, partyId); + }); + + it('should validate raw transaction', function () { + const txBuilder = new TransferRejectionBuilder(coins.get('tcanton')); + const tx = new Transaction(coins.get('tcanton')); + txBuilder.initBuilder(tx); + txBuilder.setTransaction(TransferRejectionPrepareResponse); + txBuilder.validateRawTransaction(TransferRejectionPrepareResponse.preparedTransaction); + }); + + it('should validate the transaction', function () { + const txBuilder = new TransferRejectionBuilder(coins.get('tcanton')); + const tx = new Transaction(coins.get('tcanton')); + tx.prepareCommand = TransferRejectionPrepareResponse; + txBuilder.initBuilder(tx); + txBuilder.setTransaction(TransferRejectionPrepareResponse); + txBuilder.validateTransaction(tx); + }); + + it('should throw error in validating raw transaction', function () { + const txBuilder = new TransferRejectionBuilder(coins.get('tcanton')); + const tx = new Transaction(coins.get('tcanton')); + txBuilder.initBuilder(tx); + const invalidPrepareResponse = TransferRejectionPrepareResponse; + invalidPrepareResponse.preparedTransactionHash = 'QFxX1WBdq7lZbSc45iKA3J/oOF9mrVLc3DeKphAjb15='; + txBuilder.setTransaction(invalidPrepareResponse); + try { + txBuilder.validateRawTransaction(invalidPrepareResponse.preparedTransaction); + } catch (e) { + assert.equal(e.message, 'invalid raw transaction, hash not matching'); + } + }); +}); diff --git a/modules/sdk-core/src/account-lib/baseCoin/enum.ts b/modules/sdk-core/src/account-lib/baseCoin/enum.ts index 14dd125214..2057d7c719 100644 --- a/modules/sdk-core/src/account-lib/baseCoin/enum.ts +++ b/modules/sdk-core/src/account-lib/baseCoin/enum.ts @@ -93,6 +93,8 @@ export enum TransactionType { TransferAccept, // canton transfer acknowledgement TransferAcknowledge, + // canton transfer reject, 2-step + TransferReject, // trx FREEZE,