From b5081344da8e66fbd3a5113cc3313325ef72a494 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Mon, 23 Nov 2015 14:05:51 -0800 Subject: [PATCH] Allow memos on all transaction types --- docs/index.md | 40 +++++----- docs/src/transactions.md.ejs | 6 ++ src/common/schema-validator.js | 1 + src/common/schemas/objects/memos.json | 1 + src/common/schemas/objects/settings.json | 74 +++++++++++++++++++ src/common/schemas/output/get-settings.json | 73 +----------------- .../specifications/order-cancellation.json | 3 +- src/common/schemas/specifications/order.json | 3 +- .../schemas/specifications/settings.json | 10 ++- .../schemas/specifications/trustline.json | 3 +- src/transaction/order.js | 4 + src/transaction/ordercancellation.js | 7 +- src/transaction/settings.js | 3 + src/transaction/trustline.js | 4 + 14 files changed, 137 insertions(+), 95 deletions(-) create mode 100644 src/common/schemas/objects/settings.json diff --git a/docs/index.md b/docs/index.md index 714b8f42..895ae0ea 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,6 +16,7 @@ - [Transaction Fees](#transaction-fees) - [Transaction Instructions](#transaction-instructions) - [Transaction ID](#transaction-id) + - [Transaction Memos](#transaction-memos) - [Transaction Specifications](#transaction-specifications) - [Payment](#payment) - [Trustline](#trustline) @@ -256,6 +257,16 @@ A transaction ID is a 64-bit hexadecimal string that uniquely identifies the tra You can look up a transaction by ID using the [getTransaction](#gettransaction) method. +## Transaction Memos + +Every transaction can optionally have an array of memos for user applications. The `memos` field in each [transaction specification](#transaction-specifications) is an array of objects with the following structure: + +Name | Type | Description +---- | ---- | ----------- +data | string | *Optional* Arbitrary string, conventionally containing the content of the memo. +format | string | *Optional* Conventionally containing information on how the memo is encoded, for example as a [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml). Only characters allowed in URLs are permitted. +type | string | *Optional* Conventionally, a unique relation (according to [RFC 5988](http://tools.ietf.org/html/rfc5988#section-4)) that defines the format of this memo. Only characters allowed in URLs are permitted. + # Transaction Specifications A *transaction specification* specifies what a transaction should do. Each [Transaction Type](#transaction-types) has its own type of specification. @@ -280,11 +291,7 @@ destination | object | The destination of the funds to be sent. allowPartialPayment | boolean | *Optional* A boolean that, if set to true, indicates that this payment should go through even if the whole amount cannot be delivered because of a lack of liquidity or funds in the source account account invoiceID | string | *Optional* A 256-bit hash that can be used to identify a particular payment. limitQuality | boolean | *Optional* Only take paths where all the conversions have an input:output ratio that is equal or better than the ratio of destination.amount:source.maxAmount. -memos | array | *Optional* Array of memos to attach to the transaction. -memos[] | object | Memo objects represent arbitrary data that can be included in a transaction -*memos[].* data | string | *Optional* Arbitrary string, conventionally containing the content of the memo. -*memos[].* format | string | *Optional* Conventionally containing information on how the memo is encoded, for example as a [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml). Only characters allowed in URLs are permitted. -*memos[].* type | string | *Optional* Conventionally, a unique relation (according to [RFC 5988](http://tools.ietf.org/html/rfc5988#section-4)) that defines the format of this memo. Only characters allowed in URLs are permitted. +memos | [memos](#transaction-memos) | *Optional* Array of memos to attach to the transaction. noDirectRipple | boolean | *Optional* A boolean that can be set to true if paths are specified and the sender would like the Ripple Network to disregard any direct paths from the source account to the destination account. This may be used to take advantage of an arbitrage opportunity or by gateways wishing to issue balances from a hot wallet to a user who has mistakenly set a trustline directly to the hot wallet paths | string | *Optional* The paths of trustlines and orders to use in executing the payment. @@ -324,6 +331,7 @@ counterparty | [address](#ripple-address) | The address of the account this trus limit | [value](#value) | The maximum amount that the owner of the trustline can be owed through the trustline. authorized | boolean | *Optional* If true, authorize the counterparty to hold issuances from this account. frozen | boolean | *Optional* If true, the trustline is frozen, which means that funds can only be sent to the owner. +memos | [memos](#transaction-memos) | *Optional* Array of memos to attach to the transaction. qualityIn | number | *Optional* Incoming balances on this trustline are valued at this ratio. qualityOut | number | *Optional* Outgoing balances on this trustline are valued at this ratio. ripplingDisabled | boolean | *Optional* If true, payments cannot ripple through this trustline. @@ -356,6 +364,7 @@ totalPrice | [amount](#amount) | The total price to be paid for the `quantity` t expirationTime | date-time string | *Optional* Time after which the offer is no longer active, as an [ISO 8601 date-time](https://en.wikipedia.org/wiki/ISO_8601). fillOrKill | boolean | *Optional* Treat the offer as a [Fill or Kill order](http://en.wikipedia.org/wiki/Fill_or_kill). Only attempt to match existing offers in the ledger, and only do so if the entire quantity can be exchanged. immediateOrCancel | boolean | *Optional* Treat the offer as an [Immediate or Cancel order](http://en.wikipedia.org/wiki/Immediate_or_cancel). If enabled, the offer will never become a ledger node: it only attempts to match existing offers in the ledger. +memos | [memos](#transaction-memos) | *Optional* Array of memos to attach to the transaction. passive | boolean | *Optional* 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. ### Example @@ -385,6 +394,7 @@ See [Transaction Types](#transaction-types) for a description. Name | Type | Description ---- | ---- | ----------- orderSequence | [sequence](#account-sequence-number) | The [account sequence number](#account-sequence-number) of the order to cancel. +memos | [memos](#transaction-memos) | *Optional* Array of memos to attach to the transaction. ### Example @@ -409,6 +419,7 @@ domain | string | *Optional* The domain that owns this account, as a hexadecima emailHash | string,null | *Optional* Hash of an email address to be used for generating an avatar image. Conventionally, clients use Gravatar to display this image. Use `null` to clear. enableTransactionIDTracking | boolean | *Optional* Track the ID of this account’s most recent transaction. globalFreeze | boolean | *Optional* Freeze all assets issued by this account. +memos | [memos](#transaction-memos) | *Optional* Array of memos to attach to the transaction. messageKey | string | *Optional* Public key for sending encrypted messages to this account. Conventionally, it should be a secp256k1 key, the same encryption that is used by the rest of Ripple. noFreeze | boolean | *Optional* Permanently give up the ability to freeze individual trust lines. This flag can never be disabled after being enabled. passwordSpent | boolean | *Optional* Indicates that the account has used its free SetRegularKey transaction. @@ -444,11 +455,7 @@ destination | object | Fields pertaining to the destination of the payment. allowCancelAfter | date-time string | *Optional* If present, the suspended payment may be cancelled after this time. allowExecuteAfter | date-time string | *Optional* If present, the suspended payment can not be executed before this time. digest | string | *Optional* If present, proof is required upon execution. -memos | array | *Optional* Array of memos to attach to the transaction. -memos[] | object | Memo objects represent arbitrary data that can be included in a transaction -*memos[].* data | string | *Optional* Arbitrary string, conventionally containing the content of the memo. -*memos[].* format | string | *Optional* Conventionally containing information on how the memo is encoded, for example as a [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml). Only characters allowed in URLs are permitted. -*memos[].* type | string | *Optional* Conventionally, a unique relation (according to [RFC 5988](http://tools.ietf.org/html/rfc5988#section-4)) that defines the format of this memo. Only characters allowed in URLs are permitted. +memos | [memos](#transaction-memos) | *Optional* Array of memos to attach to the transaction. ### Example @@ -484,11 +491,7 @@ Name | Type | Description ---- | ---- | ----------- owner | [address](#ripple-address) | The address of the owner of the suspended payment to cancel. suspensionSequence | [sequence](#account-sequence-number) | The [account sequence number](#account-sequence-number) of the [Suspended Payment Creation](#suspended-payment-creation) transaction for the suspended payment to cancel. -memos | array | *Optional* Array of memos to attach to the transaction. -memos[] | object | Memo objects represent arbitrary data that can be included in a transaction -*memos[].* data | string | *Optional* Arbitrary string, conventionally containing the content of the memo. -*memos[].* format | string | *Optional* Conventionally containing information on how the memo is encoded, for example as a [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml). Only characters allowed in URLs are permitted. -*memos[].* type | string | *Optional* Conventionally, a unique relation (according to [RFC 5988](http://tools.ietf.org/html/rfc5988#section-4)) that defines the format of this memo. Only characters allowed in URLs are permitted. +memos | [memos](#transaction-memos) | *Optional* Array of memos to attach to the transaction. ### Example @@ -510,11 +513,7 @@ Name | Type | Description owner | [address](#ripple-address) | The address of the owner of the suspended payment to execute. suspensionSequence | [sequence](#account-sequence-number) | The [account sequence number](#account-sequence-number) of the [Suspended Payment Creation](#suspended-payment-creation) transaction for the suspended payment to execute. digest | string | *Optional* The original `digest` from the suspended payment creation transaction. This is sha256 hash of `proof` string. It is replicated here so that the relatively expensive hashing operation can be delegated to a server without ledger history and the server with ledger history only has to do a quick comparison of the old digest with the new digest. -memos | array | *Optional* Array of memos to attach to the transaction. -memos[] | object | Memo objects represent arbitrary data that can be included in a transaction -*memos[].* data | string | *Optional* Arbitrary string, conventionally containing the content of the memo. -*memos[].* format | string | *Optional* Conventionally containing information on how the memo is encoded, for example as a [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml). Only characters allowed in URLs are permitted. -*memos[].* type | string | *Optional* Conventionally, a unique relation (according to [RFC 5988](http://tools.ietf.org/html/rfc5988#section-4)) that defines the format of this memo. Only characters allowed in URLs are permitted. +memos | [memos](#transaction-memos) | *Optional* Array of memos to attach to the transaction. method | integer | *Optional* The method for verifying the proof; only method `1` is supported. proof | string | *Optional* A value that produces the digest when hashed. It must be 32 charaters long and contain only 8-bit characters. @@ -2577,6 +2576,7 @@ domain | string | *Optional* The domain that owns this account, as a hexadecima emailHash | string,null | *Optional* Hash of an email address to be used for generating an avatar image. Conventionally, clients use Gravatar to display this image. Use `null` to clear. enableTransactionIDTracking | boolean | *Optional* Track the ID of this account’s most recent transaction. globalFreeze | boolean | *Optional* Freeze all assets issued by this account. +memos | [memos](#transaction-memos) | *Optional* Array of memos to attach to the transaction. messageKey | string | *Optional* Public key for sending encrypted messages to this account. Conventionally, it should be a secp256k1 key, the same encryption that is used by the rest of Ripple. noFreeze | boolean | *Optional* Permanently give up the ability to freeze individual trust lines. This flag can never be disabled after being enabled. passwordSpent | boolean | *Optional* Indicates that the account has used its free SetRegularKey transaction. diff --git a/docs/src/transactions.md.ejs b/docs/src/transactions.md.ejs index 7d9cd00c..96b7f91d 100644 --- a/docs/src/transactions.md.ejs +++ b/docs/src/transactions.md.ejs @@ -55,3 +55,9 @@ We recommended that you specify a `maxLedgerVersion` because without it there is A transaction ID is a 64-bit hexadecimal string that uniquely identifies the transaction. The transaction ID is derived from the transaction instruction and specifications, using a strong hash function. You can look up a transaction by ID using the [getTransaction](#gettransaction) method. + +## Transaction Memos + +Every transaction can optionally have an array of memos for user applications. The `memos` field in each [transaction specification](#transaction-specifications) is an array of objects with the following structure: + +<%- renderSchema('objects/memos.json') %> diff --git a/src/common/schema-validator.js b/src/common/schema-validator.js index 9c5bc14c..5885c771 100644 --- a/src/common/schema-validator.js +++ b/src/common/schema-validator.js @@ -43,6 +43,7 @@ function loadSchemas() { require('./schemas/objects/signed-value.json'), require('./schemas/objects/orderbook.json'), require('./schemas/objects/instructions.json'), + require('./schemas/objects/settings.json'), require('./schemas/specifications/settings.json'), require('./schemas/specifications/payment.json'), require('./schemas/specifications/suspended-payment-cancellation.json'), diff --git a/src/common/schemas/objects/memos.json b/src/common/schemas/objects/memos.json index 7d6e42c5..5ca0c230 100644 --- a/src/common/schemas/objects/memos.json +++ b/src/common/schemas/objects/memos.json @@ -1,6 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "title": "memos", + "link": "transaction-memos", "description": "Array of memos to attach to the transaction.", "type": "array", "items": { diff --git a/src/common/schemas/objects/settings.json b/src/common/schemas/objects/settings.json new file mode 100644 index 00000000..5fb97c31 --- /dev/null +++ b/src/common/schemas/objects/settings.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "settingsPlusMemos", + "type": "object", + "properties": { + "passwordSpent": { + "type": "boolean", + "description": "Indicates that the account has used its free SetRegularKey transaction." + }, + "requireDestinationTag": { + "type": "boolean", + "description": "Requires incoming payments to specify a destination tag." + }, + "requireAuthorization": { + "type": "boolean", + "description": "If set, this account must individually approve other users in order for those users to hold this account’s issuances." + }, + "disallowIncomingXRP": { + "type": "boolean", + "description": "Indicates that client applications should not send XRP to this account. Not enforced by rippled." + }, + "disableMasterKey": { + "type": "boolean", + "description": "Disallows use of the master key to sign transactions for this account." + }, + "enableTransactionIDTracking": { + "type": "boolean", + "description": "Track the ID of this account’s most recent transaction." + }, + "noFreeze": { + "type": "boolean", + "description": "Permanently give up the ability to freeze individual trust lines. This flag can never be disabled after being enabled." + }, + "globalFreeze": { + "type": "boolean", + "description": "Freeze all assets issued by this account." + }, + "defaultRipple": { + "type": "boolean", + "description": "Enable [rippling](https://ripple.com/knowledge_center/understanding-the-noripple-flag/) on this account’s trust lines by default. (New in [rippled 0.27.3](https://github.com/ripple/rippled/releases/tag/0.27.3))" + }, + "emailHash": { + "description": "Hash of an email address to be used for generating an avatar image. Conventionally, clients use Gravatar to display this image. Use `null` to clear.", + "oneOf": [ + {"type": "null"}, + {"$ref": "hash128"} + ] + }, + "messageKey": { + "type": "string", + "description": "Public key for sending encrypted messages to this account. Conventionally, it should be a secp256k1 key, the same encryption that is used by the rest of Ripple." + }, + "domain": { + "type": "string", + "description": " The domain that owns this account, as a hexadecimal string representing the ASCII for the domain in lowercase." + }, + "transferRate": { + "description": " The fee to charge when users transfer this account’s issuances, represented as billionths of a unit. Use `null` to set no fee.", + "oneOf": [ + {"type": "null"}, + {"type": "number", "minimum": 1, "maximum": 4.294967295} + ] + }, + "regularKey": { + "oneOf": [ + {"$ref": "address"}, + {"type": "null"} + ], + "description": "The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key." + }, + "memos": {"$ref": "memos"} + }, + "additionalProperties": false +} diff --git a/src/common/schemas/output/get-settings.json b/src/common/schemas/output/get-settings.json index c336bdb6..85fb38a1 100644 --- a/src/common/schemas/output/get-settings.json +++ b/src/common/schemas/output/get-settings.json @@ -1,73 +1,8 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "title": "getSettings", - "type": "object", - "properties": { - "passwordSpent": { - "type": "boolean", - "description": "Indicates that the account has used its free SetRegularKey transaction." - }, - "requireDestinationTag": { - "type": "boolean", - "description": "Requires incoming payments to specify a destination tag." - }, - "requireAuthorization": { - "type": "boolean", - "description": "If set, this account must individually approve other users in order for those users to hold this account’s issuances." - }, - "disallowIncomingXRP": { - "type": "boolean", - "description": "Indicates that client applications should not send XRP to this account. Not enforced by rippled." - }, - "disableMasterKey": { - "type": "boolean", - "description": "Disallows use of the master key to sign transactions for this account." - }, - "enableTransactionIDTracking": { - "type": "boolean", - "description": "Track the ID of this account’s most recent transaction." - }, - "noFreeze": { - "type": "boolean", - "description": "Permanently give up the ability to freeze individual trust lines. This flag can never be disabled after being enabled." - }, - "globalFreeze": { - "type": "boolean", - "description": "Freeze all assets issued by this account." - }, - "defaultRipple": { - "type": "boolean", - "description": "Enable [rippling](https://ripple.com/knowledge_center/understanding-the-noripple-flag/) on this account’s trust lines by default. (New in [rippled 0.27.3](https://github.com/ripple/rippled/releases/tag/0.27.3))" - }, - "emailHash": { - "description": "Hash of an email address to be used for generating an avatar image. Conventionally, clients use Gravatar to display this image. Use `null` to clear.", - "oneOf": [ - {"type": "null"}, - {"$ref": "hash128"} - ] - }, - "messageKey": { - "type": "string", - "description": "Public key for sending encrypted messages to this account. Conventionally, it should be a secp256k1 key, the same encryption that is used by the rest of Ripple." - }, - "domain": { - "type": "string", - "description": " The domain that owns this account, as a hexadecimal string representing the ASCII for the domain in lowercase." - }, - "transferRate": { - "description": " The fee to charge when users transfer this account’s issuances, represented as billionths of a unit. Use `null` to set no fee.", - "oneOf": [ - {"type": "null"}, - {"type": "number", "minimum": 1, "maximum": 4.294967295} - ] - }, - "regularKey": { - "oneOf": [ - {"$ref": "address"}, - {"type": "null"} - ], - "description": "The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key." - } - }, - "additionalProperties": false + "$ref": "settingsPlusMemos", + "not": { + "required": ["memos"] + } } diff --git a/src/common/schemas/specifications/order-cancellation.json b/src/common/schemas/specifications/order-cancellation.json index e8c7fb51..0122a843 100644 --- a/src/common/schemas/specifications/order-cancellation.json +++ b/src/common/schemas/specifications/order-cancellation.json @@ -7,7 +7,8 @@ "orderSequence": { "$ref": "sequence", "description": "The [account sequence number](#account-sequence-number) of the order to cancel." - } + }, + "memos": {"$ref": "memos"} }, "required": ["orderSequence"], "additionalProperties": false diff --git a/src/common/schemas/specifications/order.json b/src/common/schemas/specifications/order.json index 933754ad..84e5cb9b 100644 --- a/src/common/schemas/specifications/order.json +++ b/src/common/schemas/specifications/order.json @@ -33,7 +33,8 @@ "type": "string", "format": "date-time", "description": "Time after which the offer is no longer active, as an [ISO 8601 date-time](https://en.wikipedia.org/wiki/ISO_8601)." - } + }, + "memos": {"$ref": "memos"} }, "required": ["direction", "quantity", "totalPrice"], "additionalProperties": false, diff --git a/src/common/schemas/specifications/settings.json b/src/common/schemas/specifications/settings.json index 3ce375de..85442955 100644 --- a/src/common/schemas/specifications/settings.json +++ b/src/common/schemas/specifications/settings.json @@ -2,11 +2,17 @@ "$schema": "http://json-schema.org/draft-04/schema#", "title": "settings", "link": "settings", - "allOf": [ + "$ref": "settingsPlusMemos", + "oneOf": [ { - "$ref": "getSettings" + "required": ["memos"], + "minProperties": 2, + "maxProperties": 2 }, { + "not": { + "required": ["memos"] + }, "minProperties": 1, "maxProperties": 1 } diff --git a/src/common/schemas/specifications/trustline.json b/src/common/schemas/specifications/trustline.json index f2a02d2a..92bb8ebb 100644 --- a/src/common/schemas/specifications/trustline.json +++ b/src/common/schemas/specifications/trustline.json @@ -35,7 +35,8 @@ "frozen": { "type": "boolean", "description": "If true, the trustline is frozen, which means that funds can only be sent to the owner." - } + }, + "memos": {"$ref": "memos"} }, "required": ["currency", "counterparty", "limit"], "additionalProperties": false diff --git a/src/transaction/order.js b/src/transaction/order.js index b4da99e3..c7589044 100644 --- a/src/transaction/order.js +++ b/src/transaction/order.js @@ -1,5 +1,6 @@ /* @flow */ 'use strict'; +const _ = require('lodash'); const utils = require('./utils'); const offerFlags = utils.common.txFlags.OfferCreate; const {validate, iso8601ToRippleTime} = utils.common; @@ -34,6 +35,9 @@ function createOrderTransaction(account: string, order: Order): Object { if (order.expirationTime !== undefined) { txJSON.Expiration = iso8601ToRippleTime(order.expirationTime); } + if (order.memos !== undefined) { + txJSON.Memos = _.map(order.memos, utils.convertMemo); + } return txJSON; } diff --git a/src/transaction/ordercancellation.js b/src/transaction/ordercancellation.js index 6736fd37..08b4ec7e 100644 --- a/src/transaction/ordercancellation.js +++ b/src/transaction/ordercancellation.js @@ -1,5 +1,6 @@ /* @flow */ 'use strict'; +const _ = require('lodash'); const utils = require('./utils'); const validate = utils.common.validate; import type {Instructions, Prepare} from './types.js'; @@ -7,11 +8,15 @@ import type {Instructions, Prepare} from './types.js'; function createOrderCancellationTransaction(account: string, orderCancellation: Object ): Object { - return { + const txJSON: Object = { TransactionType: 'OfferCancel', Account: account, OfferSequence: orderCancellation.orderSequence }; + if (orderCancellation.memos !== undefined) { + txJSON.Memos = _.map(orderCancellation.memos, utils.convertMemo); + } + return txJSON; } function prepareOrderCancellation(address: string, orderCancellation: Object, diff --git a/src/transaction/settings.js b/src/transaction/settings.js index 138e98d3..0c71284b 100644 --- a/src/transaction/settings.js +++ b/src/transaction/settings.js @@ -93,6 +93,9 @@ function createSettingsTransaction(account: string, settings: Settings if (txJSON.TransferRate !== undefined) { txJSON.TransferRate = convertTransferRate(txJSON.TransferRate); } + if (settings.memos !== undefined) { + txJSON.Memos = _.map(settings.memos, utils.convertMemo); + } return txJSON; } diff --git a/src/transaction/trustline.js b/src/transaction/trustline.js index b9ba7ad0..865c068b 100644 --- a/src/transaction/trustline.js +++ b/src/transaction/trustline.js @@ -1,5 +1,6 @@ /* @flow */ 'use strict'; +const _ = require('lodash'); const utils = require('./utils'); const validate = utils.common.validate; const trustlineFlags = utils.common.txFlags.TrustSet; @@ -44,6 +45,9 @@ function createTrustlineTransaction(account: string, txJSON.Flags |= trustline.frozen ? trustlineFlags.SetFreeze : trustlineFlags.ClearFreeze; } + if (trustline.memos !== undefined) { + txJSON.Memos = _.map(trustline.memos, utils.convertMemo); + } return txJSON; }