Skip to content

Commit 7c7a78c

Browse files
author
Greg S
committed
Issue #89: added Recipient class, added Namespace.createFromEncoded, added TransferTransaction.recipient Recipient capability
1 parent 29193b9 commit 7c7a78c

File tree

7 files changed

+280
-9
lines changed

7 files changed

+280
-9
lines changed

src/infrastructure/transaction/CreateTransactionFromDTO.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import {Address} from '../../model/account/Address';
1818
import {PublicAccount} from '../../model/account/PublicAccount';
19+
import {Recipient} from '../../model/account/Recipient';
1920
import {NetworkType} from '../../model/blockchain/NetworkType';
2021
import {Mosaic} from '../../model/mosaic/Mosaic';
2122
import {MosaicId} from '../../model/mosaic/MosaicId';
@@ -116,12 +117,13 @@ export const CreateTransactionFromDTO = (transactionDTO): Transaction => {
116117
*/
117118
const CreateStandaloneTransactionFromDTO = (transactionDTO, transactionInfo): Transaction => {
118119
if (transactionDTO.type === TransactionType.TRANSFER) {
120+
const recipient = Recipient.createFromEncoded(transactionDTO.recipient);
119121
return new TransferTransaction(
120122
extractNetworkType(transactionDTO.version),
121123
extractTransactionVersion(transactionDTO.version),
122124
Deadline.createFromDTO(transactionDTO.deadline),
123125
new UInt64(transactionDTO.fee || [0, 0]),
124-
Address.createFromEncoded(transactionDTO.recipient),
126+
recipient.value,
125127
transactionDTO.mosaics === undefined ? [] :
126128
transactionDTO.mosaics
127129
.map((mosaicDTO) => new Mosaic(new MosaicId(mosaicDTO.id), new UInt64(mosaicDTO.amount))),

src/model/account/Recipient.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2019 NEM
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {NamespaceId} from '../namespace/NamespaceId';
18+
import {Address} from './Address';
19+
20+
/**
21+
* The Recipient structure describes a recipient with either `namespaceId` or `address` filled.
22+
*/
23+
export class Recipient {
24+
25+
/**
26+
* @param value
27+
*/
28+
constructor(/**
29+
* The encoded (hexadecimal) recipient notation
30+
*/
31+
public readonly value: Address | NamespaceId) {
32+
}
33+
34+
/**
35+
* Create a Recipient object from its encoded hexadecimal notation
36+
* @param encoded
37+
*/
38+
public static createFromEncoded(encoded: string): Recipient {
39+
40+
// If bit 0 of byte 0 is not set (like in 0x90), then it is a regular address.
41+
// Else (e.g. 0x91) it represents a namespace id which starts at byte 1.
42+
const fstByteBit0 = encoded.substr(1, 1);
43+
44+
if (parseInt(fstByteBit0, 2) === 1) {
45+
// namespaceId encoded hexadecimal notation provided
46+
// only 8 bytes are relevant to resolve the NamespaceId
47+
const relevantPart = encoded.substr(2, 16);
48+
const namespaceId = NamespaceId.createFromEncoded(relevantPart);
49+
return new Recipient(namespaceId);
50+
}
51+
52+
// read address from encoded hexadecimal notation
53+
const address = Address.createFromEncoded(encoded);
54+
return new Recipient(address);
55+
}
56+
57+
/**
58+
* Compares recipients for equality
59+
* @param recipient - Recipient
60+
* @returns {boolean}
61+
*/
62+
public equals(recipient: Recipient): boolean {
63+
64+
if (this.value instanceof NamespaceId && recipient.value instanceof NamespaceId) {
65+
return (this.value as NamespaceId).equals(recipient.value as NamespaceId);
66+
} else if (this.value instanceof Address && recipient.value instanceof Address) {
67+
return (this.value as Address).equals(recipient.value as Address);
68+
}
69+
70+
return false;
71+
}
72+
}

src/model/namespace/NamespaceId.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import {namespaceId as NamespaceIdGenerator} from 'nem2-library';
16+
import {convert, namespaceId as NamespaceIdGenerator} from 'nem2-library';
1717
import {Id} from '../Id';
1818

1919
/**
@@ -48,6 +48,18 @@ export class NamespaceId {
4848
}
4949
}
5050

51+
/**
52+
* Create a NamespaceId object from its encoded hexadecimal notation.
53+
* @param encoded
54+
* @returns {NamespaceId}
55+
*/
56+
public static createFromEncoded(encoded: string): NamespaceId {
57+
const uint = convert.hexToUint8(encoded).reverse();
58+
const hex = convert.uint8ToHex(uint);
59+
const namespace = new NamespaceId(Id.fromHex(hex).toDTO());
60+
return namespace;
61+
}
62+
5163
/**
5264
* Get string value of id
5365
* @returns {string}

src/model/transaction/TransferTransaction.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { TransferTransaction as TransferTransactionLibrary, VerifiableTransaction } from 'nem2-library';
1818
import { Address } from '../account/Address';
1919
import { PublicAccount } from '../account/PublicAccount';
20+
import { Recipient } from '../account/Recipient';
2021
import { NetworkType } from '../blockchain/NetworkType';
2122
import { Mosaic } from '../mosaic/Mosaic';
2223
import { NamespaceId } from '../namespace/NamespaceId';
@@ -42,7 +43,7 @@ export class TransferTransaction extends Transaction {
4243
* @returns {TransferTransaction}
4344
*/
4445
public static create(deadline: Deadline,
45-
recipient: Address | NamespaceId,
46+
recipient: Recipient | Address | NamespaceId,
4647
mosaics: Mosaic[],
4748
message: Message,
4849
networkType: NetworkType): TransferTransaction {
@@ -74,7 +75,7 @@ export class TransferTransaction extends Transaction {
7475
/**
7576
* The address of the recipient.
7677
*/
77-
public readonly recipient: Address | NamespaceId,
78+
public readonly recipient: Recipient | Address | NamespaceId,
7879
/**
7980
* The array of Mosaic objects.
8081
*/
@@ -95,13 +96,20 @@ export class TransferTransaction extends Transaction {
9596
* @returns {string}
9697
*/
9798
public recipientToString(): string {
98-
if (this.recipient instanceof NamespaceId) {
99-
// namespaceId available, return hexadecimal notation
100-
return (this.recipient as NamespaceId).toHex();
99+
100+
// handle `Recipient` wrapper class
101+
let recipient = this.recipient;
102+
if (recipient instanceof Recipient) {
103+
recipient = (this.recipient as Recipient).value;
104+
}
105+
106+
if (recipient instanceof NamespaceId) {
107+
// namespaceId recipient, return hexadecimal notation
108+
return (recipient as NamespaceId).toHex();
101109
}
102110

103-
// address available
104-
return (this.recipient as Address).plain();
111+
// address recipient
112+
return (recipient as Address).plain();
105113
}
106114

107115
/**
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2019 NEM
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {deepEqual} from 'assert';
18+
import {expect} from 'chai';
19+
import {Address} from '../../../src/model/account/Address';
20+
import {Recipient} from '../../../src/model/account/Recipient';
21+
import {Id} from '../../../src/model/Id';
22+
import {NamespaceId} from '../../../src/model/namespace/NamespaceId';
23+
24+
describe('Recipient', () => {
25+
it('should be created with address given Address value', () => {
26+
const recipient = new Recipient(Address.createFromRawAddress('SBILTA367K2LX2FEXG5TFWAS7GEFYAGY7QLFBYKC'));
27+
expect(recipient.value).to.be.instanceof(Address);
28+
expect((recipient.value as Address).plain()).to.be.equal('SBILTA367K2LX2FEXG5TFWAS7GEFYAGY7QLFBYKC');
29+
});
30+
31+
it('should be created with namespaceId given NamespaceId value', () => {
32+
const recipient = new Recipient(new NamespaceId('nem'));
33+
expect(recipient.value).to.be.instanceof(NamespaceId);
34+
expect((recipient.value as NamespaceId).toHex()).to.be.equal('84b3552d375ffa4b');
35+
});
36+
37+
it('should be created with address given encoded address recipient', () => {
38+
const recipient = Recipient.createFromEncoded('9050B9837EFAB4BBE8A4B9BB32D812F9885C00D8FC1650E142');
39+
const expectAddress = 'SBILTA367K2LX2FEXG5TFWAS7GEFYAGY7QLFBYKC';
40+
41+
expect(recipient.value).to.be.instanceof(Address);
42+
expect((recipient.value as Address).plain()).to.be.equal(expectAddress);
43+
});
44+
45+
const ns_vectors = [
46+
{name: 'nem', encoded: '914BFA5F372D55B38400000000000000000000000000000000'},
47+
{name: 'nem.owner', encoded: '9151776168D24257D800000000000000000000000000000000'},
48+
];
49+
50+
it('should be created with namespaceId given encoded namespaceId recipient', () => {
51+
ns_vectors.map(({name, encoded}) => {
52+
const recipient = Recipient.createFromEncoded(encoded);
53+
const expectNamespaceId = new NamespaceId(name);
54+
55+
expect(recipient.value).to.be.instanceof(NamespaceId);
56+
expect((recipient.value as NamespaceId).toHex()).to.be.equal(expectNamespaceId.toHex());
57+
});
58+
});
59+
60+
const addr_vectors = [
61+
{
62+
address: 'SBILTA367K2LX2FEXG5TFWAS7GEFYAGY7QLFBYKC',
63+
encoded: '9050B9837EFAB4BBE8A4B9BB32D812F9885C00D8FC1650E142',
64+
},
65+
{
66+
address: 'NAR3W7B4BCOZSZMFIZRYB3N5YGOUSWIYJCJ6HDFG',
67+
encoded: '6823BB7C3C089D996585466380EDBDC19D4959184893E38CA6',
68+
},
69+
];
70+
71+
it('should be created with namespaceId given encoded namespaceId recipient', () => {
72+
addr_vectors.map(({address, encoded}) => {
73+
const recipient = Recipient.createFromEncoded(encoded);
74+
const expectAddress = Address.createFromRawAddress(address);
75+
76+
expect(recipient.value).to.be.instanceof(Address);
77+
expect((recipient.value as Address).plain()).to.be.equal(expectAddress.plain());
78+
});
79+
});
80+
81+
it('should compare and return false for equals with different types', () => {
82+
const recipient1 = Recipient.createFromEncoded('9050B9837EFAB4BBE8A4B9BB32D812F9885C00D8FC1650E142');
83+
const recipient2 = Recipient.createFromEncoded('914BFA5F372D55B38400000000000000000000000000000000');
84+
expect(recipient1.equals(recipient2)).to.be.equal(false);
85+
});
86+
87+
it('should compare and return false for equals with different values of type address', () => {
88+
const recipient1 = Recipient.createFromEncoded('9050B9837EFAB4BBE8A4B9BB32D812F9885C00D8FC1650E142');
89+
const recipient2 = Recipient.createFromEncoded('6823BB7C3C089D996585466380EDBDC19D4959184893E38CA6');
90+
expect(recipient1.value).to.be.instanceof(Address);
91+
expect(recipient2.value).to.be.instanceof(Address);
92+
expect(recipient1.equals(recipient2)).to.be.equal(false);
93+
});
94+
95+
it('should compare and return false for equals with different values of type namespaceId', () => {
96+
const recipient1 = Recipient.createFromEncoded('914BFA5F372D55B38400000000000000000000000000000000');
97+
const recipient2 = Recipient.createFromEncoded('9151776168D24257D800000000000000000000000000000000');
98+
expect(recipient1.value).to.be.instanceof(NamespaceId);
99+
expect(recipient2.value).to.be.instanceof(NamespaceId);
100+
expect(recipient1.equals(recipient2)).to.be.equal(false);
101+
});
102+
});

test/model/namespace/NamespaceId.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,21 @@ describe('NamespaceId', () => {
3737
deepEqual(id.id, new Id([3646934825, 3576016193]));
3838
expect(id.fullName).to.be.equal(undefined);
3939
});
40+
41+
const vectors = [
42+
{encoded: '4bfa5f372d55b384', uint: [929036875, 2226345261]}, // new NamespaceId('nem')
43+
{encoded: '08a12f89ee5a49f8', uint: [2301600008, 4165556974]}, // new NamespaceId('nem.owner.test1')
44+
{encoded: '1f810565e8f4aeab', uint: [1694859551, 2880369896]}, // new NamespaceId('nem.owner.test2')
45+
{encoded: '552d1c0a2bc9b8ae', uint: [169618773, 2931345707]}, // new NamespaceId('nem.owner.test3')
46+
{encoded: 'bfca1440d49ae090', uint: [1075104447, 2430638804]}, // new NamespaceId('nem.owner.test4')
47+
{encoded: 'ccf10b96814211ab', uint: [2517365196, 2870035073]}, // new NamespaceId('nem.owner.test5')
48+
];
49+
50+
it('should be created from encoded vectors', () => {
51+
vectors.map(({encoded, uint}) => {
52+
const fromHex = NamespaceId.createFromEncoded(encoded.toUpperCase());
53+
const fromId = new NamespaceId(uint);
54+
deepEqual(fromId.id, fromHex.id);
55+
});
56+
});
4057
});

test/model/transaction/TransferTransaction.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { expect } from 'chai';
1818
import { Account } from '../../../src/model/account/Account';
1919
import { Address } from '../../../src/model/account/Address';
20+
import { Recipient } from '../../../src/model/account/Recipient';
2021
import { NetworkType } from '../../../src/model/blockchain/NetworkType';
2122
import { NetworkCurrencyMosaic } from '../../../src/model/mosaic/NetworkCurrencyMosaic';
2223
import { NamespaceId } from '../../../src/model/namespace/NamespaceId';
@@ -107,6 +108,63 @@ describe('TransferTransaction', () => {
107108
'44B262C46CEABB8500E1F50500000000');
108109
});
109110

111+
it('should createComplete an TransferTransaction object with Recipient given address recipient', () => {
112+
const addressRecipient = new Recipient(Address.createFromRawAddress('SBILTA367K2LX2FEXG5TFWAS7GEFYAGY7QLFBYKC'));
113+
const transferTransaction = TransferTransaction.create(
114+
Deadline.create(),
115+
addressRecipient,
116+
[
117+
NetworkCurrencyMosaic.createRelative(100),
118+
],
119+
PlainMessage.create('test-message'),
120+
NetworkType.MIJIN_TEST,
121+
);
122+
123+
expect(transferTransaction.message.payload).to.be.equal('test-message');
124+
expect(transferTransaction.mosaics.length).to.be.equal(1);
125+
expect(transferTransaction.recipient).to.be.instanceof(Recipient);
126+
127+
const address = ((transferTransaction.recipient as Recipient).value as Address);
128+
expect(address.plain()).to.be.equal('SBILTA367K2LX2FEXG5TFWAS7GEFYAGY7QLFBYKC');
129+
130+
const signedTransaction = transferTransaction.signWith(account);
131+
132+
expect(signedTransaction.payload.substring(
133+
240,
134+
signedTransaction.payload.length,
135+
)).to.be.equal('9050B9837EFAB4BBE8A4B9BB32D812F9885C00D8FC1650E1420D000100746573742D6D657373616765' +
136+
'44B262C46CEABB8500E1F50500000000');
137+
});
138+
139+
it('should createComplete an TransferTransaction object with Recipient given namespaceId recipient', () => {
140+
const namespaceId = new NamespaceId('nem.owner');
141+
const namespaceRecipient = new Recipient(namespaceId);
142+
const transferTransaction = TransferTransaction.create(
143+
Deadline.create(),
144+
namespaceRecipient,
145+
[
146+
NetworkCurrencyMosaic.createRelative(100),
147+
],
148+
PlainMessage.create('test-message'),
149+
NetworkType.MIJIN_TEST,
150+
);
151+
152+
expect(transferTransaction.message.payload).to.be.equal('test-message');
153+
expect(transferTransaction.mosaics.length).to.be.equal(1);
154+
expect(transferTransaction.recipient).to.be.instanceof(Recipient);
155+
156+
const actualNamespaceId = ((transferTransaction.recipient as Recipient).value as NamespaceId);
157+
expect(actualNamespaceId.toHex()).to.be.equal(namespaceId.toHex());
158+
159+
const signedTransaction = transferTransaction.signWith(account);
160+
161+
expect(signedTransaction.payload.substring(
162+
240,
163+
signedTransaction.payload.length,
164+
)).to.be.equal('9151776168D24257D8000000000000000000000000000000000D000100746573742D6D657373616765' +
165+
'44B262C46CEABB8500E1F50500000000');
166+
});
167+
110168
it('should format TransferTransaction payload with 25 bytes binary address', () => {
111169
const transferTransaction = TransferTransaction.create(
112170
Deadline.create(),

0 commit comments

Comments
 (0)