Skip to content

Commit

Permalink
Support operations with muxed accounts. (#337)
Browse files Browse the repository at this point in the history
Allow operations to receive muxed account in `destination` and `source`.
  • Loading branch information
abuiles authored Apr 23, 2020
1 parent 9455d66 commit 4009c3a
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 47 deletions.
23 changes: 13 additions & 10 deletions src/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import isFinite from 'lodash/isFinite';
import { best_r } from './util/continued_fraction';
import { Asset } from './asset';
import { StrKey } from './strkey';
import { Keypair } from './keypair';
import xdr from './generated/stellar-xdr_generated';
import * as ops from './operations/index';

Expand Down Expand Up @@ -62,12 +61,13 @@ export const AuthImmutableFlag = 1 << 2;
export class Operation {
static setSourceAccount(opAttributes, opts) {
if (opts.source) {
if (!StrKey.isValidEd25519PublicKey(opts.source)) {
try {
opAttributes.sourceAccount = xdr.MuxedAccount.fromXDR(
StrKey.decodeMuxedAccount(opts.source)
);
} catch (e) {
throw new Error('Source address is invalid');
}
opAttributes.sourceAccount = Keypair.fromPublicKey(
opts.source
).xdrMuxedAccount();
}
}

Expand All @@ -81,10 +81,13 @@ export class Operation {
function accountIdtoAddress(accountId) {
return StrKey.encodeEd25519PublicKey(accountId.ed25519());
}
function muxedAccounttoAddress(accountId) {
return StrKey.encodeMuxedAccount(accountId.toXDR());
}

const result = {};
if (operation.sourceAccount()) {
result.source = accountIdtoAddress(operation.sourceAccount());
result.source = muxedAccounttoAddress(operation.sourceAccount());
}

const attrs = operation.body().value();
Expand All @@ -99,7 +102,7 @@ export class Operation {
}
case 'payment': {
result.type = 'payment';
result.destination = accountIdtoAddress(attrs.destination());
result.destination = muxedAccounttoAddress(attrs.destination());
result.asset = Asset.fromOperation(attrs.asset());
result.amount = this._fromXDRAmount(attrs.amount());
break;
Expand All @@ -108,7 +111,7 @@ export class Operation {
result.type = 'pathPaymentStrictReceive';
result.sendAsset = Asset.fromOperation(attrs.sendAsset());
result.sendMax = this._fromXDRAmount(attrs.sendMax());
result.destination = accountIdtoAddress(attrs.destination());
result.destination = muxedAccounttoAddress(attrs.destination());
result.destAsset = Asset.fromOperation(attrs.destAsset());
result.destAmount = this._fromXDRAmount(attrs.destAmount());
result.path = [];
Expand All @@ -125,7 +128,7 @@ export class Operation {
result.type = 'pathPaymentStrictSend';
result.sendAsset = Asset.fromOperation(attrs.sendAsset());
result.sendAmount = this._fromXDRAmount(attrs.sendAmount());
result.destination = accountIdtoAddress(attrs.destination());
result.destination = muxedAccounttoAddress(attrs.destination());
result.destAsset = Asset.fromOperation(attrs.destAsset());
result.destMin = this._fromXDRAmount(attrs.destMin());
result.path = [];
Expand Down Expand Up @@ -230,7 +233,7 @@ export class Operation {
}
case 'accountMerge': {
result.type = 'accountMerge';
result.destination = accountIdtoAddress(attrs);
result.destination = muxedAccounttoAddress(attrs);
break;
}
case 'manageDatum': {
Expand Down
10 changes: 5 additions & 5 deletions src/operations/account_merge.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import xdr from '../generated/stellar-xdr_generated';
import { Keypair } from '../keypair';
import { StrKey } from '../strkey';

/**
Expand All @@ -13,12 +12,13 @@ import { StrKey } from '../strkey';
*/
export function accountMerge(opts) {
const opAttributes = {};
if (!StrKey.isValidEd25519PublicKey(opts.destination)) {
try {
opAttributes.body = xdr.OperationBody.accountMerge(
xdr.MuxedAccount.fromXDR(StrKey.decodeMuxedAccount(opts.destination))
);
} catch (e) {
throw new Error('destination is invalid');
}
opAttributes.body = xdr.OperationBody.accountMerge(
Keypair.fromPublicKey(opts.destination).xdrMuxedAccount()
);
this.setSourceAccount(opAttributes, opts);

return new xdr.Operation(opAttributes);
Expand Down
15 changes: 9 additions & 6 deletions src/operations/path_payment_strict_receive.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import xdr from '../generated/stellar-xdr_generated';
import { Keypair } from '../keypair';
import { StrKey } from '../strkey';

/**
Expand All @@ -24,8 +23,6 @@ export function pathPaymentStrictReceive(opts) {
throw new Error('Must specify a send asset');
case !this.isValidAmount(opts.sendMax):
throw new TypeError(this.constructAmountRequirementsError('sendMax'));
case !StrKey.isValidEd25519PublicKey(opts.destination):
throw new Error('destination is invalid');
case !opts.destAsset:
throw new Error('Must provide a destAsset for a payment operation');
case !this.isValidAmount(opts.destAmount):
Expand All @@ -37,9 +34,15 @@ export function pathPaymentStrictReceive(opts) {
const attributes = {};
attributes.sendAsset = opts.sendAsset.toXDRObject();
attributes.sendMax = this._toXDRAmount(opts.sendMax);
attributes.destination = Keypair.fromPublicKey(
opts.destination
).xdrMuxedAccount();

try {
attributes.destination = xdr.MuxedAccount.fromXDR(
StrKey.decodeMuxedAccount(opts.destination)
);
} catch (e) {
throw new Error('destination is invalid');
}

attributes.destAsset = opts.destAsset.toXDRObject();
attributes.destAmount = this._toXDRAmount(opts.destAmount);

Expand Down
13 changes: 7 additions & 6 deletions src/operations/path_payment_strict_send.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import xdr from '../generated/stellar-xdr_generated';
import { Keypair } from '../keypair';
import { StrKey } from '../strkey';

/**
Expand All @@ -24,8 +23,6 @@ export function pathPaymentStrictSend(opts) {
throw new Error('Must specify a send asset');
case !this.isValidAmount(opts.sendAmount):
throw new TypeError(this.constructAmountRequirementsError('sendAmount'));
case !StrKey.isValidEd25519PublicKey(opts.destination):
throw new Error('destination is invalid');
case !opts.destAsset:
throw new Error('Must provide a destAsset for a payment operation');
case !this.isValidAmount(opts.destMin):
Expand All @@ -37,9 +34,13 @@ export function pathPaymentStrictSend(opts) {
const attributes = {};
attributes.sendAsset = opts.sendAsset.toXDRObject();
attributes.sendAmount = this._toXDRAmount(opts.sendAmount);
attributes.destination = Keypair.fromPublicKey(
opts.destination
).xdrMuxedAccount();
try {
attributes.destination = xdr.MuxedAccount.fromXDR(
StrKey.decodeMuxedAccount(opts.destination)
);
} catch (e) {
throw new Error('destination is invalid');
}
attributes.destAsset = opts.destAsset.toXDRObject();
attributes.destMin = this._toXDRAmount(opts.destMin);

Expand Down
15 changes: 8 additions & 7 deletions src/operations/payment.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import xdr from '../generated/stellar-xdr_generated';
import { Keypair } from '../keypair';
import { StrKey } from '../strkey';

/**
Expand All @@ -14,9 +13,6 @@ import { StrKey } from '../strkey';
* @returns {xdr.PaymentOp} Payment operation
*/
export function payment(opts) {
if (!StrKey.isValidEd25519PublicKey(opts.destination)) {
throw new Error('destination is invalid');
}
if (!opts.asset) {
throw new Error('Must provide an asset for a payment operation');
}
Expand All @@ -25,9 +21,14 @@ export function payment(opts) {
}

const attributes = {};
attributes.destination = Keypair.fromPublicKey(
opts.destination
).xdrMuxedAccount();
try {
attributes.destination = xdr.MuxedAccount.fromXDR(
StrKey.decodeMuxedAccount(opts.destination)
);
} catch (e) {
throw new Error('destination is invalid');
}

attributes.asset = opts.asset.toXDRObject();
attributes.amount = this._toXDRAmount(opts.amount);
const paymentOp = new xdr.PaymentOp(attributes);
Expand Down
135 changes: 122 additions & 13 deletions test/unit/operation_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ describe('Operation', function() {
).to.be.equal('10000000000');
expect(obj.startingBalance).to.be.equal(startingBalance);
});

it('fails to create createAccount operation with an invalid destination address', function() {
let opts = {
destination: 'GCEZW',
Expand Down Expand Up @@ -80,15 +79,31 @@ describe('Operation', function() {
var obj = StellarBase.Operation.fromXDRObject(operation);
expect(obj.type).to.be.equal('payment');
expect(obj.destination).to.be.equal(destination);
expect(
operation
.body()
.value()
.amount()
.toString()
).to.be.equal('10000000000');
expect(obj.amount).to.be.equal(amount);
expect(obj.asset.equals(asset)).to.be.true;
});
it('supports muxed accounts', function() {
var destination =
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6';
var amount = '1000.0000000';
var asset = new StellarBase.Asset(
'USDUSD',
'GDGU5OAPHNPU5UCLE5RDJHG7PXZFQYWKCFOEXSXNMR6KRQRI5T6XXCD7'
);
var source =
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6';
let op = StellarBase.Operation.payment({
destination,
asset,
amount,
source
});
var xdr = op.toXDR('hex');
var operation = StellarBase.xdr.Operation.fromXDR(
Buffer.from(xdr, 'hex')
);
var obj = StellarBase.Operation.fromXDRObject(operation);
expect(obj.type).to.be.equal('payment');
expect(obj.destination).to.be.equal(destination);
expect(obj.source).to.be.equal(source);
});

it('fails to create payment operation with an invalid destination address', function() {
Expand Down Expand Up @@ -180,7 +195,48 @@ describe('Operation', function() {
'GDTNXRLOJD2YEBPKK7KCMR7J33AAG5VZXHAJTHIG736D6LVEFLLLKPDL'
);
});

it('supports muxed accounts', function() {
var sendAsset = new StellarBase.Asset(
'USD',
'GDGU5OAPHNPU5UCLE5RDJHG7PXZFQYWKCFOEXSXNMR6KRQRI5T6XXCD7'
);
var sendMax = '3.0070000';
var destination =
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6';
var source =
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6';
var destAsset = new StellarBase.Asset(
'USD',
'GDGU5OAPHNPU5UCLE5RDJHG7PXZFQYWKCFOEXSXNMR6KRQRI5T6XXCD7'
);
var destAmount = '3.1415000';
var path = [
new StellarBase.Asset(
'USD',
'GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB'
),
new StellarBase.Asset(
'EUR',
'GDTNXRLOJD2YEBPKK7KCMR7J33AAG5VZXHAJTHIG736D6LVEFLLLKPDL'
)
];
let op = StellarBase.Operation.pathPaymentStrictReceive({
sendAsset,
sendMax,
destination,
destAsset,
destAmount,
path,
source
});
var xdr = op.toXDR('hex');
var operation = StellarBase.xdr.Operation.fromXDR(
Buffer.from(xdr, 'hex')
);
var obj = StellarBase.Operation.fromXDRObject(operation);
expect(obj.destination).to.be.equal(destination);
expect(obj.source).to.be.equal(destination);
});
it('fails to create path payment operation with an invalid destination address', function() {
let opts = {
destination: 'GCEZW',
Expand Down Expand Up @@ -296,7 +352,45 @@ describe('Operation', function() {
'GDTNXRLOJD2YEBPKK7KCMR7J33AAG5VZXHAJTHIG736D6LVEFLLLKPDL'
);
});

it('supports muxed accounts', function() {
var sendAsset = new StellarBase.Asset(
'USD',
'GDGU5OAPHNPU5UCLE5RDJHG7PXZFQYWKCFOEXSXNMR6KRQRI5T6XXCD7'
);
var sendAmount = '3.0070000';
var destination =
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6';
var source =
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6';
var destAsset = new StellarBase.Asset(
'USD',
'GDGU5OAPHNPU5UCLE5RDJHG7PXZFQYWKCFOEXSXNMR6KRQRI5T6XXCD7'
);
var destMin = '3.1415000';
var path = [
new StellarBase.Asset(
'USD',
'GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB'
)
];
let op = StellarBase.Operation.pathPaymentStrictSend({
sendAsset,
sendAmount,
destination,
destAsset,
destMin,
path,
source
});
var xdr = op.toXDR('hex');
var operation = StellarBase.xdr.Operation.fromXDR(
Buffer.from(xdr, 'hex')
);
var obj = StellarBase.Operation.fromXDRObject(operation);
expect(obj.type).to.be.equal('pathPaymentStrictSend');
expect(obj.destination).to.be.equal(destination);
expect(obj.source).to.be.equal(source);
});
it('fails to create path payment operation with an invalid destination address', function() {
let opts = {
destination: 'GCEZW',
Expand Down Expand Up @@ -1443,7 +1537,22 @@ describe('Operation', function() {
expect(obj.type).to.be.equal('accountMerge');
expect(obj.destination).to.be.equal(opts.destination);
});

it('supports muxed accounts', function() {
var opts = {};
opts.destination =
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6';
opts.source =
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6';
let op = StellarBase.Operation.accountMerge(opts);
var xdr = op.toXDR('hex');
var operation = StellarBase.xdr.Operation.fromXDR(
Buffer.from(xdr, 'hex')
);
var obj = StellarBase.Operation.fromXDRObject(operation);
expect(obj.type).to.be.equal('accountMerge');
expect(obj.destination).to.be.equal(opts.destination);
expect(obj.source).to.be.equal(opts.source);
});
it('fails to create accountMerge operation with an invalid destination address', function() {
let opts = {
destination: 'GCEZW'
Expand Down

0 comments on commit 4009c3a

Please sign in to comment.