diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2cf302f4..66085729 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -205,8 +205,8 @@ } }, "ripple-lib-transactionparser": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.5.1.tgz" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.6.0.tgz" }, "ws": { "version": "0.7.2", diff --git a/package.json b/package.json index 475e4ce8..f5f4f295 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "ripple-binary-codec": "^0.0.6", "ripple-hashes": "^0.0.1", "ripple-keypairs": "^0.10.0", - "ripple-lib-transactionparser": "^0.5.1", + "ripple-lib-transactionparser": "^0.6.0", "ws": "~0.7.1" }, "devDependencies": { diff --git a/src/common/schemas/order-change.json b/src/common/schemas/order-change.json index ef377ac5..8148d090 100644 --- a/src/common/schemas/order-change.json +++ b/src/common/schemas/order-change.json @@ -7,11 +7,12 @@ "type": "string", "enum": ["buy", "sell"] }, - "quantity": {"$ref": "balance"}, - "totalPrice": {"$ref": "balance"}, + "quantity": {"$ref": "amount"}, + "totalPrice": {"$ref": "amount"}, "makerExchangeRate": {"$ref": "value"}, "sequence": {"$ref": "sequence"}, - "status": {"enum": ["created", "open", "closed", "canceled"]} + "status": {"enum": ["created", "filled", "partially-filled", "cancelled"]}, + "expirationTime": {"type": "string", "format": "date-time"} }, "required": ["direction", "quantity", "totalPrice", "sequence", "status"], "additionalProperties": false diff --git a/src/common/schemas/order.json b/src/common/schemas/order.json index 2c368b0e..f11f044c 100644 --- a/src/common/schemas/order.json +++ b/src/common/schemas/order.json @@ -14,7 +14,8 @@ "passive": { "description": "If enabled, the offer will not consume offers that exactly match it, and instead becomes an Offer node in the ledger. It will still consume offers that cross it.", "type": "boolean" - } + }, + "expirationTime": {"type": "string", "format": "date-time"} }, "required": ["direction", "quantity", "totalPrice"], "additionalProperties": false, diff --git a/src/ledger/parse/account-order.js b/src/ledger/parse/account-order.js index 20c777f8..008ab418 100644 --- a/src/ledger/parse/account-order.js +++ b/src/ledger/parse/account-order.js @@ -26,7 +26,9 @@ function parseAccountOrder(address: string, order: Object): Object { direction: direction, quantity: quantity, totalPrice: totalPrice, - passive: ((order.flags & flags.Passive) !== 0) || undefined + passive: ((order.flags & flags.Passive) !== 0) || undefined, + // rippled currently does not provide "expiration" in account_offers + expirationTime: utils.parseTimestamp(order.expiration) }); const properties = { diff --git a/src/ledger/parse/order.js b/src/ledger/parse/order.js index 902d4b89..c80d6a95 100644 --- a/src/ledger/parse/order.js +++ b/src/ledger/parse/order.js @@ -21,7 +21,8 @@ function parseOrder(tx: Object): Object { passive: ((tx.Flags & flags.Passive) !== 0) || undefined, immediateOrCancel: ((tx.Flags & flags.ImmediateOrCancel) !== 0) || undefined, - fillOrKill: ((tx.Flags & flags.FillOrKill) !== 0) || undefined + fillOrKill: ((tx.Flags & flags.FillOrKill) !== 0) || undefined, + expirationTime: utils.parseTimestamp(tx.Expiration) }); } diff --git a/src/ledger/parse/orderbook-order.js b/src/ledger/parse/orderbook-order.js index f1499428..298a26dd 100644 --- a/src/ledger/parse/orderbook-order.js +++ b/src/ledger/parse/orderbook-order.js @@ -18,7 +18,8 @@ function parseOrderbookOrder(order: Object): Object { direction: direction, quantity: quantity, totalPrice: totalPrice, - passive: ((order.Flags & flags.Passive) !== 0) || undefined + passive: ((order.Flags & flags.Passive) !== 0) || undefined, + expirationTime: utils.parseTimestamp(order.Expiration) }); const properties = { diff --git a/src/ledger/parse/utils.js b/src/ledger/parse/utils.js index 4a1e0f31..dcf70853 100644 --- a/src/ledger/parse/utils.js +++ b/src/ledger/parse/utils.js @@ -18,8 +18,8 @@ function adjustQualityForXRP( (new BigNumber(quality)).shift(shift).toString(); } -function parseTimestamp(tx: {date: string}): string | void { - return tx.date ? (new Date(rippleToUnixTimestamp(tx.date))).toISOString() +function parseTimestamp(date: number): string | void { + return date ? (new Date(rippleToUnixTimestamp(date))).toISOString() : undefined; } @@ -55,7 +55,7 @@ function parseOutcome(tx: Object): ?Object { return { result: tx.meta.TransactionResult, - timestamp: parseTimestamp(tx), + timestamp: parseTimestamp(tx.date), fee: utils.common.dropsToXrp(tx.Fee), balanceChanges: balanceChanges, orderbookChanges: orderbookChanges, @@ -85,6 +85,7 @@ module.exports = { parseOutcome, parseMemos, hexToString, + parseTimestamp, adjustQualityForXRP, dropsToXrp: utils.common.dropsToXrp, constants: utils.common.constants, diff --git a/src/transaction/order.js b/src/transaction/order.js index b9243da1..7ab2c863 100644 --- a/src/transaction/order.js +++ b/src/transaction/order.js @@ -3,6 +3,7 @@ const utils = require('./utils'); const validate = utils.common.validate; const offerFlags = utils.common.txFlags.OfferCreate; +const unixToRippleTimestamp = utils.common.unixToRippleTimestamp; import type {Instructions, Prepare} from './types.js'; import type {Order} from '../ledger/transaction-types.js'; @@ -34,6 +35,9 @@ function createOrderTransaction(account: string, order: Order): Object { if (order.fillOrKill === true) { txJSON.Flags |= offerFlags.FillOrKill; } + if (order.expirationTime !== undefined) { + txJSON.Expiration = unixToRippleTimestamp(Date.parse(order.expirationTime)); + } return txJSON; } diff --git a/test/api-test.js b/test/api-test.js index 38badb64..21755dad 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -83,14 +83,22 @@ describe('RippleAPI', function() { }); it('prepareOrder - buy order', function() { - return this.api.prepareOrder(address, requests.prepareOrder, instructions) - .then(_.partial(checkResult, responses.prepareOrder, 'prepare')); + const request = requests.prepareOrder.buy; + return this.api.prepareOrder(address, request, instructions) + .then(_.partial(checkResult, responses.prepareOrder.buy, 'prepare')); + }); + + it('prepareOrder - buy order with expiration', function() { + const request = requests.prepareOrder.expiration; + const response = responses.prepareOrder.expiration; + return this.api.prepareOrder(address, request, instructions) + .then(_.partial(checkResult, response, 'prepare')); }); it('prepareOrder - sell order', function() { - return this.api.prepareOrder( - address, requests.prepareOrderSell, instructions).then( - _.partial(checkResult, responses.prepareOrderSell, 'prepare')); + const request = requests.prepareOrder.sell; + return this.api.prepareOrder(address, request, instructions).then( + _.partial(checkResult, responses.prepareOrder.sell, 'prepare')); }); it('prepareOrderCancellation', function() { @@ -280,6 +288,15 @@ describe('RippleAPI', function() { 'getTransaction')); }); + it('getTransaction - order with expiration cancellation', function() { + const hash = + '097B9491CC76B64831F1FEA82EAA93BCD728106D90B65A072C933888E946C40B'; + return this.api.getTransaction(hash).then( + _.partial(checkResult, + responses.getTransaction.orderWithExpirationCancellation, + 'getTransaction')); + }); + it('getTransaction - trustline set', function() { const hash = '635A0769BD94710A1F6A76CDE65A3BC661B20B798807D1BBBDADCEA26420538D'; diff --git a/test/fixtures/api/requests/index.js b/test/fixtures/api/requests/index.js index 5660b449..e49cfd66 100644 --- a/test/fixtures/api/requests/index.js +++ b/test/fixtures/api/requests/index.js @@ -1,8 +1,11 @@ 'use strict'; module.exports = { - prepareOrder: require('./prepare-order'), - prepareOrderSell: require('./prepare-order-sell'), + prepareOrder: { + buy: require('./prepare-order'), + sell: require('./prepare-order-sell'), + expiration: require('./prepare-order-expiration') + }, preparePayment: require('./prepare-payment'), preparePaymentAllOptions: require('./prepare-payment-all-options'), preparePaymentNoCounterparty: require('./prepare-payment-no-counterparty'), diff --git a/test/fixtures/api/requests/prepare-order-expiration.json b/test/fixtures/api/requests/prepare-order-expiration.json new file mode 100644 index 00000000..5ffdce97 --- /dev/null +++ b/test/fixtures/api/requests/prepare-order-expiration.json @@ -0,0 +1,14 @@ +{ + "direction": "buy", + "quantity": { + "currency": "USD", + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "value": "10.1" + }, + "totalPrice": { + "currency": "XRP", + "value": "2" + }, + "immediateOrCancel": true, + "expirationTime": "2015-01-14T18:36:52.000Z" +} diff --git a/test/fixtures/api/responses/get-orderbook.json b/test/fixtures/api/responses/get-orderbook.json index e99613eb..a9cb5beb 100644 --- a/test/fixtures/api/responses/get-orderbook.json +++ b/test/fixtures/api/responses/get-orderbook.json @@ -52,7 +52,8 @@ "currency": "BTC", "value": "0.3", "counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" - } + }, + "expirationTime": "2014-12-25T01:14:43.000Z" }, "properties": { "maker": "raudnGKfTK23YKfnS7ixejHrqGERTYNFXk", @@ -104,7 +105,8 @@ "currency": "BTC", "value": "0.4499999999999999", "counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" - } + }, + "expirationTime": "2014-12-25T01:14:44.000Z" }, "properties": { "maker": "raudnGKfTK23YKfnS7ixejHrqGERTYNFXk", @@ -144,7 +146,8 @@ "currency": "BTC", "value": "0.5", "counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" - } + }, + "expirationTime": "2014-12-25T00:41:38.000Z" }, "properties": { "maker": "rDVBvAQScXrGRGnzrxRrcJPeNLeLeUTAqE", @@ -196,7 +199,8 @@ "currency": "BTC", "value": "0.8", "counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" - } + }, + "expirationTime": "2014-12-25T00:41:39.000Z" }, "properties": { "maker": "rDVBvAQScXrGRGnzrxRrcJPeNLeLeUTAqE", @@ -290,7 +294,8 @@ "currency": "BTC", "value": "0.4499999999999999", "counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" - } + }, + "expirationTime": "2014-12-25T01:14:44.000Z" }, "properties": { "maker": "raudnGKfTK23YKfnS7ixejHrqGERTYNFXk", @@ -322,7 +327,8 @@ "currency": "BTC", "value": "0.8", "counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" - } + }, + "expirationTime": "2014-12-24T21:44:11.000Z" }, "properties": { "maker": "rDVBvAQScXrGRGnzrxRrcJPeNLeLeUTAqE", @@ -434,7 +440,8 @@ "currency": "BTC", "value": "1.6", "counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" - } + }, + "expirationTime": "2014-12-24T21:44:12.000Z" }, "properties": { "maker": "rDVBvAQScXrGRGnzrxRrcJPeNLeLeUTAqE", diff --git a/test/fixtures/api/responses/get-transaction-order-cancellation.json b/test/fixtures/api/responses/get-transaction-order-cancellation.json index 91c83861..ff8c9a24 100644 --- a/test/fixtures/api/responses/get-transaction-order-cancellation.json +++ b/test/fixtures/api/responses/get-transaction-order-cancellation.json @@ -25,15 +25,15 @@ "quantity": { "currency": "USD", "counterparty": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", - "value": "0" + "value": "237" }, "totalPrice": { "currency": "XRP", - "value": "0" + "value": "0.0002" }, "makerExchangeRate": "1185000", "sequence": 465, - "status": "canceled" + "status": "cancelled" } ] }, diff --git a/test/fixtures/api/responses/get-transaction-order-with-expiration-cancellation.json b/test/fixtures/api/responses/get-transaction-order-with-expiration-cancellation.json new file mode 100644 index 00000000..d053c3af --- /dev/null +++ b/test/fixtures/api/responses/get-transaction-order-with-expiration-cancellation.json @@ -0,0 +1,44 @@ +{ + "type": "orderCancellation", + "address": "rBSZe33F5oxHTbxSF1nZJooVDpcrrqNFp3", + "sequence": 1122979, + "id": "097B9491CC76B64831F1FEA82EAA93BCD728106D90B65A072C933888E946C40B", + "specification": { + "orderSequence": 1122978 + }, + "outcome": { + "result": "tesSUCCESS", + "timestamp": "2015-01-14T18:27:00.000Z", + "fee": "0.011", + "balanceChanges": { + "rBSZe33F5oxHTbxSF1nZJooVDpcrrqNFp3": [ + { + "currency": "XRP", + "value": "-0.011" + } + ] + }, + "orderbookChanges": { + "rBSZe33F5oxHTbxSF1nZJooVDpcrrqNFp3": [ + { + "direction": "buy", + "quantity": { + "currency": "CNY", + "counterparty": "rnuF96W4SZoCJmbHYBFoJZpR8eCaxNvekK", + "value": "3200" + }, + "totalPrice": { + "currency": "XRP", + "value": "34700.537395" + }, + "sequence": 1122978, + "status": "cancelled", + "makerExchangeRate": "0.09221759200942773", + "expirationTime": "2015-01-14T18:36:52.000Z" + } + ] + }, + "ledgerVersion": 11119599, + "indexInLedger": 15 + } +} diff --git a/test/fixtures/api/responses/get-transaction-payment.json b/test/fixtures/api/responses/get-transaction-payment.json index 9bba564a..a1f65cd2 100644 --- a/test/fixtures/api/responses/get-transaction-payment.json +++ b/test/fixtures/api/responses/get-transaction-payment.json @@ -68,16 +68,16 @@ "direction": "buy", "quantity": { "currency": "XRP", - "value": "-1.101198" + "value": "1.101198" }, "totalPrice": { "currency": "USD", "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", - "value": "-0.001002" + "value": "0.001002" }, "makerExchangeRate": "1099", "sequence": 58, - "status": "open" + "status": "partially-filled" } ] }, diff --git a/test/fixtures/api/responses/get-transactions.json b/test/fixtures/api/responses/get-transactions.json index f83ef635..4effe849 100644 --- a/test/fixtures/api/responses/get-transactions.json +++ b/test/fixtures/api/responses/get-transactions.json @@ -74,16 +74,16 @@ "direction": "buy", "quantity": { "currency": "XRP", - "value": "-1.101198" + "value": "1.101198" }, "totalPrice": { "currency": "USD", "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", - "value": "-0.001002" + "value": "0.001002" }, "makerExchangeRate": "1099", "sequence": 58, - "status": "open" + "status": "partially-filled" } ] }, @@ -166,16 +166,16 @@ "direction": "buy", "quantity": { "currency": "XRP", - "value": "-1.101198" + "value": "1.101198" }, "totalPrice": { "currency": "USD", "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", - "value": "-0.001002" + "value": "0.001002" }, "makerExchangeRate": "1099", "sequence": 58, - "status": "open" + "status": "partially-filled" } ] }, diff --git a/test/fixtures/api/responses/index.js b/test/fixtures/api/responses/index.js index 14d5a354..b2cf8c4e 100644 --- a/test/fixtures/api/responses/index.js +++ b/test/fixtures/api/responses/index.js @@ -17,6 +17,8 @@ module.exports = { getSettings: require('./get-settings.json'), getTransaction: { orderCancellation: require('./get-transaction-order-cancellation.json'), + orderWithExpirationCancellation: + require('./get-transaction-order-with-expiration-cancellation.json'), order: require('./get-transaction-order.json'), payment: require('./get-transaction-payment.json'), settings: require('./get-transaction-settings.json'), @@ -36,8 +38,11 @@ module.exports = { withSettingsTx: require('./get-ledger-with-settings-tx') }, prepareOrderCancellation: require('./prepare-order-cancellation.json'), - prepareOrder: require('./prepare-order.json'), - prepareOrderSell: require('./prepare-order-sell.json'), + prepareOrder: { + buy: require('./prepare-order.json'), + sell: require('./prepare-order-sell.json'), + expiration: require('./prepare-order-expiration') + }, preparePayment: { normal: require('./prepare-payment.json'), allOptions: require('./prepare-payment-all-options.json'), diff --git a/test/fixtures/api/responses/prepare-order-expiration.json b/test/fixtures/api/responses/prepare-order-expiration.json new file mode 100644 index 00000000..50a9e1f9 --- /dev/null +++ b/test/fixtures/api/responses/prepare-order-expiration.json @@ -0,0 +1,8 @@ +{ + "txJSON": "{\"Flags\":2147614720,\"TransactionType\":\"OfferCreate\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TakerGets\":\"2000000\",\"TakerPays\":{\"value\":\"10.1\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23,\"Expiration\":474575812}", + "instructions": { + "fee": "0.000012", + "sequence": 23, + "maxLedgerVersion": 8820051 + } +} diff --git a/test/fixtures/api/rippled/index.js b/test/fixtures/api/rippled/index.js index 7ec599c7..592fb7ae 100644 --- a/test/fixtures/api/rippled/index.js +++ b/test/fixtures/api/rippled/index.js @@ -43,6 +43,7 @@ module.exports = { NoLedgerIndex: require('./tx/no-ledger-index.json'), NoLedgerFound: require('./tx/no-ledger-found.json'), LedgerWithoutTime: require('./tx/ledger-without-time.json'), - NotValidated: require('./tx/not-validated.json') + NotValidated: require('./tx/not-validated.json'), + OfferWithExpiration: require('./tx/order-with-expiration.json') } }; diff --git a/test/fixtures/api/rippled/tx/order-with-expiration.json b/test/fixtures/api/rippled/tx/order-with-expiration.json new file mode 100644 index 00000000..fca2c4c4 --- /dev/null +++ b/test/fixtures/api/rippled/tx/order-with-expiration.json @@ -0,0 +1,98 @@ +{ + "id": 0, + "status": "success", + "type": "response", + "result": { + "Account": "rBSZe33F5oxHTbxSF1nZJooVDpcrrqNFp3", + "Fee": "11000", + "Flags": 2147483648, + "LastLedgerSequence": 11119601, + "OfferSequence": 1122978, + "Sequence": 1122979, + "SigningPubKey": "03FD8927D4450E5B6C060BF7E46D1DDA2B24C547A45D43926741095D8FCA6A71DB", + "TransactionType": "OfferCancel", + "TxnSignature": "304402207758C80B90667A407299B2D8A16F8D6DF51E7103B562529AB8242B14B737D9B10220431095B7881C4363C3A2AB966C95190DF2E438FBB394F65014B6436E56F4F6E6", + "date": 474575220, + "hash": "097B9491CC76B64831F1FEA82EAA93BCD728106D90B65A072C933888E946C40B", + "inLedger": 11119599, + "ledger_index": 11119599, + "meta": { + "AffectedNodes": [ + { + "DeletedNode": { + "FinalFields": { + "Account": "rBSZe33F5oxHTbxSF1nZJooVDpcrrqNFp3", + "BookDirectory": "94A08655B7E5C048769B82A900C085FD1D8A28B2A9E7939C4D20C32421605AB5", + "BookNode": "0000000000000000", + "Expiration": 474575812, + "Flags": 0, + "OwnerNode": "0000000000000003", + "PreviousTxnID": "40FA69EF2F42729DEE5F3BE0D43FAAB63C35FF5A28C3221A385EAFE84733C208", + "PreviousTxnLgrSeq": 11119599, + "Sequence": 1122978, + "TakerGets": "34700537395", + "TakerPays": { + "currency": "CNY", + "issuer": "rnuF96W4SZoCJmbHYBFoJZpR8eCaxNvekK", + "value": "3200" + } + }, + "LedgerEntryType": "Offer", + "LedgerIndex": "40A3657C011B5E1EACBEECB40B1BEAA1220645EA89EA8AEC2D562192B8E60095" + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Flags": 0, + "IndexPrevious": "0000000000000001", + "Owner": "rBSZe33F5oxHTbxSF1nZJooVDpcrrqNFp3", + "RootIndex": "3D3DA923D48E02DB1C0B667FA9E2777C348CBE229C7E4E83CBBDE5851D80FF32" + }, + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "8EB5EC4A94AB9D4142DB695F9503CEB4E471E803A76FD563C9B0A598281D085A" + } + }, + { + "ModifiedNode": { + "FinalFields": { + "ExchangeRate": "4D20C32421605AB5", + "Flags": 0, + "RootIndex": "94A08655B7E5C048769B82A900C085FD1D8A28B2A9E7939C4D20C32421605AB5", + "TakerGetsCurrency": "0000000000000000000000000000000000000000", + "TakerGetsIssuer": "0000000000000000000000000000000000000000", + "TakerPaysCurrency": "000000000000000000000000434E590000000000", + "TakerPaysIssuer": "35DD7DF146893456296BF4061FBE68735D28F328" + }, + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "94A08655B7E5C048769B82A900C085FD1D8A28B2A9E7939C4D20C32421605AB5" + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Account": "rBSZe33F5oxHTbxSF1nZJooVDpcrrqNFp3", + "Balance": "266777347375", + "Flags": 0, + "OwnerCount": 18, + "RegularKey": "rDpVpTMogkwzoq2mkNRBmMCxbmUAwPvoFt", + "Sequence": 1122980 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "9DE2C31C24122AEDCD6CBE74567B2AF1CE9A5B31795E60F7A6BBD48BA1304E37", + "PreviousFields": { + "Balance": "266777358375", + "OwnerCount": 19, + "Sequence": 1122979 + }, + "PreviousTxnID": "40FA69EF2F42729DEE5F3BE0D43FAAB63C35FF5A28C3221A385EAFE84733C208", + "PreviousTxnLgrSeq": 11119599 + } + } + ], + "TransactionIndex": 15, + "TransactionResult": "tesSUCCESS" + }, + "validated": true + } +} diff --git a/test/mock-rippled.js b/test/mock-rippled.js index cb8d93af..5bd84484 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -198,6 +198,9 @@ module.exports = function(port) { conn.send(createResponse(request, fixtures.tx.NotValidated)); } else if (request.transaction === hashes.NOTFOUND_TRANSACTION_HASH) { conn.send(createResponse(request, fixtures.tx.NotFound)); + } else if (request.transaction === + '097B9491CC76B64831F1FEA82EAA93BCD728106D90B65A072C933888E946C40B') { + conn.send(createResponse(request, fixtures.tx.OfferWithExpiration)); } else { assert(false, 'Unrecognized transaction hash: ' + request.transaction); }