mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-22 12:45:50 +00:00
Reorg tutorials to match nav, and update links
This commit is contained in:
@@ -0,0 +1,707 @@
|
||||
---
|
||||
html: assign-a-regular-key-pair.html
|
||||
parent: manage-account-settings.html
|
||||
seo:
|
||||
description: Authorize a second key pair to sign transactions from your account. This key pair can be changed or removed later.
|
||||
labels:
|
||||
- Security
|
||||
- Accounts
|
||||
---
|
||||
# Assign a Regular Key Pair
|
||||
|
||||
The XRP Ledger allows an account to authorize a secondary key pair, called a _[regular key pair](../../../concepts/accounts/cryptographic-keys.md)_, to sign future transactions. If the private key of a regular key pair is compromised, you can remove or replace it without changing the rest of your [account](../../../concepts/accounts/accounts.md) and re-establishing its relationships to other accounts. You can also rotate a regular key pair proactively. (Neither of those things is possible for the master key pair of an account, which is intrinsically linked to the account's address.)
|
||||
|
||||
For more information about master and regular key pairs, see [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md).
|
||||
|
||||
This tutorial walks through the steps required to assign a regular key pair to your account:
|
||||
|
||||
1. [Generate a key pair](#1-generate-a-key-pair)
|
||||
2. [Assign the key pair to your account as a regular key pair](#2-assign-the-key-pair-to-your-account-as-a-regular-key-pair)
|
||||
3. [Verify the regular key pair](#3-verify-the-regular-key-pair)
|
||||
4. [Explore next steps](#see-also)
|
||||
|
||||
|
||||
## 1. Generate a Key Pair
|
||||
|
||||
Generate a key pair that you'll assign to your account as a regular key pair.
|
||||
|
||||
This key pair is the same data type as a master key pair, so you can generate it the same way: you can use the client library of your choice or use the [wallet_propose method][] of a server you run. This might look as follows:
|
||||
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
// Request:
|
||||
|
||||
{
|
||||
"command": "wallet_propose"
|
||||
}
|
||||
|
||||
// Response:
|
||||
|
||||
{
|
||||
"result": {
|
||||
"account_id": "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7",
|
||||
"key_type": "secp256k1",
|
||||
"master_key": "KNEW BENT LYNN LED GAD BEN KENT SHAM HOBO RINK WALT ALLY",
|
||||
"master_seed": "sh8i92YRnEjJy3fpFkL8txQSCVo79",
|
||||
"master_seed_hex": "966C0F68643EFBA50D58D191D4CA8AA7",
|
||||
"public_key": "aBRNH5wUurfhZcoyR6nRwDSa95gMBkovBJ8V4cp1C1pM28H7EPL1",
|
||||
"public_key_hex": "03AEEFE1E8ED4BBC009DE996AC03A8C6B5713B1554794056C66E5B8D1753C7DD0E"
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
// Request:
|
||||
|
||||
{
|
||||
"method": "wallet_propose"
|
||||
}
|
||||
|
||||
// Response:
|
||||
|
||||
{
|
||||
"result": {
|
||||
"account_id": "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7",
|
||||
"key_type": "secp256k1",
|
||||
"master_key": "KNEW BENT LYNN LED GAD BEN KENT SHAM HOBO RINK WALT ALLY",
|
||||
"master_seed": "sh8i92YRnEjJy3fpFkL8txQSCVo79",
|
||||
"master_seed_hex": "966C0F68643EFBA50D58D191D4CA8AA7",
|
||||
"public_key": "aBRNH5wUurfhZcoyR6nRwDSa95gMBkovBJ8V4cp1C1pM28H7EPL1",
|
||||
"public_key_hex": "03AEEFE1E8ED4BBC009DE996AC03A8C6B5713B1554794056C66E5B8D1753C7DD0E",
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
$ rippled wallet_propose
|
||||
|
||||
{
|
||||
"result" : {
|
||||
"account_id" : "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7",
|
||||
"key_type" : "secp256k1",
|
||||
"master_key" : "KNEW BENT LYNN LED GAD BEN KENT SHAM HOBO RINK WALT ALLY",
|
||||
"master_seed" : "sh8i92YRnEjJy3fpFkL8txQSCVo79",
|
||||
"master_seed_hex" : "966C0F68643EFBA50D58D191D4CA8AA7",
|
||||
"public_key" : "aBRNH5wUurfhZcoyR6nRwDSa95gMBkovBJ8V4cp1C1pM28H7EPL1",
|
||||
"public_key_hex" : "03AEEFE1E8ED4BBC009DE996AC03A8C6B5713B1554794056C66E5B8D1753C7DD0E",
|
||||
"status" : "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
```py
|
||||
keypair = xrpl.wallet.Wallet.create()
|
||||
print("seed:", keypair.seed)
|
||||
print("classic address:", keypair.address)
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
const keypair = new xrpl.Wallet()
|
||||
console.log("seed:", keypair.seed)
|
||||
console.log("classic address:", keypair.classicAddress)
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
```java
|
||||
WalletFactory walletFactory = DefaultWalletFactory.getInstance();
|
||||
Wallet keypair = walletFactory.randomWallet(true).wallet();
|
||||
System.out.println(keypair);
|
||||
System.out.println(keypair.privateKey().get());
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
In the next step, you'll use the address from this response (`account_id` in the API response) to assign the key pair as a regular key pair to your account. Also, save the seed value from this key pair (`master_seed` in the API response) somewhere securely; you'll use that key to sign transactions later. (Everything else, you can forget about.)
|
||||
|
||||
|
||||
## 2. Assign the Key Pair to Your Account as a Regular Key Pair
|
||||
|
||||
Use a [SetRegularKey transaction][] to assign the key pair you generated in step 1 to your account as a regular key pair.
|
||||
|
||||
When assigning a regular key pair to your account for the first time, the SetRegularKey transaction requires signing with your account's master private key (secret). There are [several ways of securely signing transactions](../../../concepts/transactions/secure-signing.md), but this tutorial uses a local `rippled` server.
|
||||
|
||||
When you send later SetRegularKey transactions, you can sign using the existing regular private key to replace or [remove itself](change-or-remove-a-regular-key-pair.md). Note that you should still not submit your regular private key across the network.
|
||||
|
||||
|
||||
### Sign Your Transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-sign-step.md" /%}
|
||||
|
||||
|
||||
Populate the request fields with the following values:
|
||||
|
||||
| Request Field | Value |
|
||||
|:--------------|:-------------------------------------------------------------|
|
||||
| `Account` | The address of your account. |
|
||||
| `RegularKey` | `account_id` generated in step 1. |
|
||||
| `secret` | `master_key`, `master_seed`, or `master_seed_hex` (master private key) for your account. |
|
||||
|
||||
|
||||
#### Request Format
|
||||
|
||||
An example of the request format:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"command": "sign",
|
||||
"tx_json": {
|
||||
"TransactionType": "SetRegularKey",
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"RegularKey": "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7"
|
||||
},
|
||||
"secret": "ssCATR7CBvn4GLd1UuU2bqqQffHki"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"method": "sign",
|
||||
"params": [
|
||||
{
|
||||
"tx_json": {
|
||||
"TransactionType": "SetRegularKey",
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"RegularKey": "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7"
|
||||
},
|
||||
"secret": "ssCATR7CBvn4GLd1UuU2bqqQffHki"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
#Syntax: sign secret tx_json
|
||||
rippled sign ssCATR7CBvn4GLd1UuU2bqqQffHki '{"TransactionType": "SetRegularKey", "Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93", "RegularKey": "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7"}'
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
#### Response Format
|
||||
|
||||
An example of a successful response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"tx_blob": "1200052280000000240000000468400000000000000A73210384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A7446304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C268114830923439D307E642CED308FD91EF701A7BAA74788141620D685FB08D81A70D0B668749CF2E130EA7540",
|
||||
"tx_json": {
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"RegularKey": "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7",
|
||||
"Sequence": 4,
|
||||
"SigningPubKey": "0384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A",
|
||||
"TransactionType": "SetRegularKey",
|
||||
"TxnSignature": "304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C26",
|
||||
"hash": "AB73BBF7C99061678B59FB48D72CA0F5FC6DD2815B6736C6E9EB94439EC236CE"
|
||||
}
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"status": "success",
|
||||
"tx_blob": "1200052280000000240000000768400000000000000A73210384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A7446304402201453CA3D4D17F0EE3828B9E3D6ACF65327F5D4FC2BA30953CACF6CBCB4145E3502202F2154BED1D7462CAC1E3DBB31864E48C3BA0B3133ACA5E37EC54F0D0C339E2D8114830923439D307E642CED308FD91EF701A7BAA74788141620D685FB08D81A70D0B668749CF2E130EA7540",
|
||||
"tx_json": {
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"RegularKey": "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7",
|
||||
"Sequence": 4,
|
||||
"SigningPubKey": "0384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A",
|
||||
"TransactionType": "SetRegularKey",
|
||||
"TxnSignature": "304402201453CA3D4D17F0EE3828B9E3D6ACF65327F5D4FC2BA30953CACF6CBCB4145E3502202F2154BED1D7462CAC1E3DBB31864E48C3BA0B3133ACA5E37EC54F0D0C339E2D",
|
||||
"hash": "AB73BBF7C99061678B59FB48D72CA0F5FC6DD2815B6736C6E9EB94439EC236CE"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```json
|
||||
{
|
||||
"result" : {
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200052280000000240000000768400000000000000A73210384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A7446304402201453CA3D4D17F0EE3828B9E3D6ACF65327F5D4FC2BA30953CACF6CBCB4145E3502202F2154BED1D7462CAC1E3DBB31864E48C3BA0B3133ACA5E37EC54F0D0C339E2D8114830923439D307E642CED308FD91EF701A7BAA74788141620D685FB08D81A70D0B668749CF2E130EA7540",
|
||||
"tx_json" : {
|
||||
"Account" : "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"RegularKey" : "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7",
|
||||
"Sequence" : 4,
|
||||
"SigningPubKey" : "0384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A",
|
||||
"TransactionType" : "SetRegularKey",
|
||||
"TxnSignature" : "304402201453CA3D4D17F0EE3828B9E3D6ACF65327F5D4FC2BA30953CACF6CBCB4145E3502202F2154BED1D7462CAC1E3DBB31864E48C3BA0B3133ACA5E37EC54F0D0C339E2D",
|
||||
"hash" : "AB73BBF7C99061678B59FB48D72CA0F5FC6DD2815B6736C6E9EB94439EC236CE"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The `sign` command response contains a `tx_blob` value, as shown above. The offline signing response contains a `signedTransaction` value. Both are signed binary representations (blobs) of the transaction.
|
||||
|
||||
Next, use the `submit` command to send the transaction blob (`tx_blob` or `signedTransaction`) to the network.
|
||||
|
||||
|
||||
### Submit Your Transaction
|
||||
|
||||
Take the `signedTransaction` value from the offline signing response or the `tx_blob` value from the `sign` command response and submit it as the `tx_blob` value using the [submit method][].
|
||||
|
||||
#### Request Format
|
||||
|
||||
An example of the request format:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"command": "submit",
|
||||
"tx_blob": "1200052280000000240000000468400000000000000A73210384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A7446304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C268114830923439D307E642CED308FD91EF701A7BAA74788141620D685FB08D81A70D0B668749CF2E130EA7540"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"method":"submit",
|
||||
"params": [
|
||||
{
|
||||
"tx_blob": "1200052280000000240000000468400000000000000A73210384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A7446304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C268114830923439D307E642CED308FD91EF701A7BAA74788141620D685FB08D81A70D0B668749CF2E130EA7540"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
#Syntax: submit tx_blob
|
||||
rippled submit 1200052280000000240000000468400000000000000A73210384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A7446304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C268114830923439D307E642CED308FD91EF701A7BAA74788141620D685FB08D81A70D0B668749CF2E130EA7540
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
#### Response Format
|
||||
|
||||
An example of a successful response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"tx_blob": "1200052280000000240000000468400000000000000A73210384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A7446304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C268114830923439D307E642CED308FD91EF701A7BAA74788141620D685FB08D81A70D0B668749CF2E130EA7540",
|
||||
"tx_json": {
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"RegularKey": "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7",
|
||||
"Sequence": 4,
|
||||
"SigningPubKey": "0384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A",
|
||||
"TransactionType": "SetRegularKey",
|
||||
"TxnSignature": "304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C26",
|
||||
"hash": "AB73BBF7C99061678B59FB48D72CA0F5FC6DD2815B6736C6E9EB94439EC236CE"
|
||||
}
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"status": "success",
|
||||
"tx_blob": "1200052280000000240000000468400000000000000A73210384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A7446304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C268114830923439D307E642CED308FD91EF701A7BAA74788141620D685FB08D81A70D0B668749CF2E130EA7540",
|
||||
"tx_json": {
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"RegularKey": "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7",
|
||||
"Sequence": 4,
|
||||
"SigningPubKey": "0384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A",
|
||||
"TransactionType": "SetRegularKey",
|
||||
"TxnSignature": "304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C26",
|
||||
"hash": "AB73BBF7C99061678B59FB48D72CA0F5FC6DD2815B6736C6E9EB94439EC236CE"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```json
|
||||
{
|
||||
"result" : {
|
||||
"engine_result" : "tesSUCCESS",
|
||||
"engine_result_code" : 0,
|
||||
"engine_result_message" : "The transaction was applied. Only final in a validated ledger.",
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200052280000000240000000468400000000000000A73210384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A7446304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C268114830923439D307E642CED308FD91EF701A7BAA74788141620D685FB08D81A70D0B668749CF2E130EA7540",
|
||||
"tx_json" : {
|
||||
"Account" : "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"RegularKey" : "rsprUqu6BHAffAeG4HpSdjBNvnA6gdnZV7",
|
||||
"Sequence" : 4,
|
||||
"SigningPubKey" : "0384CA3C528F10C75F26E0917F001338BD3C9AA1A39B9FBD583DFFFD96CF2E2D7A",
|
||||
"TransactionType" : "SetRegularKey",
|
||||
"TxnSignature" : "304402204BCD5663F3A2BA02D2CE374439096EC6D27273522CD6E6E0BDBFB518730EAAE402200ECD02D8D2525D6FA4642613E71E395ECCEA01C42C35A668BF092A00EB649C26",
|
||||
"hash" : "AB73BBF7C99061678B59FB48D72CA0F5FC6DD2815B6736C6E9EB94439EC236CE"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
Note that the response contains a `hash` of the transaction, which you can use to [look up the transaction's final outcome](../../../references/http-websocket-apis/public-api-methods/transaction-methods/tx.md).
|
||||
|
||||
|
||||
## 3. Verify the Regular Key Pair
|
||||
|
||||
At this point, the regular key pair is assigned to your account and you should be able to send transactions using the regular key pair. **To avoid losing control of your account,** it is important that you test your regular key before you take any additional steps such as [disabling the master key pair](disable-master-key-pair.md). If you make a mistake and lose access to your account, no one can restore it for you.
|
||||
|
||||
To verify that your account has the regular key pair set correctly, submit an [AccountSet transaction][] from your account, signing it with the regular private key you assigned to your account in step 2. As in step 1, this tutorial uses a local `rippled` server as a [way of securely signing transactions](../../../concepts/transactions/secure-signing.md).
|
||||
|
||||
|
||||
### Sign Your Transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-sign-step.md" /%}
|
||||
|
||||
|
||||
Populate the request fields with the following values:
|
||||
|
||||
| Request Field | Value |
|
||||
|:--------------|:-------------------------------------------------------------|
|
||||
| `Account` | The address of your account. |
|
||||
| `secret` | `master_key`, `master_seed`, or `master_seed_hex` (regular private key) generated in step 1 and assigned to your account in step 2. |
|
||||
|
||||
|
||||
#### Request Format
|
||||
|
||||
Here's an example of the request format. Note that the request does not include any `AccountSet` options. This means that a successful transaction has no effect other than to confirm that the regular key pair is set correctly for your account (and to destroy the transaction cost).
|
||||
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"command": "sign",
|
||||
"tx_json": {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93"
|
||||
},
|
||||
"secret": "sh8i92YRnEjJy3fpFkL8txQSCVo79"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"method": "sign",
|
||||
"params": [
|
||||
{
|
||||
"tx_json": {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93"
|
||||
},
|
||||
"secret": "sh8i92YRnEjJy3fpFkL8txQSCVo79"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
#Syntax: sign secret tx_json
|
||||
rippled sign sh8i92YRnEjJy3fpFkL8txQSCVo79 '{"TransactionType": "AccountSet", "Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93"}'
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
#### Response Format
|
||||
|
||||
An example of a successful response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"tx_blob": "1200032280000000240000000468400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB88114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json": {
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 4,
|
||||
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType": "AccountSet",
|
||||
"TxnSignature": "3045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB8",
|
||||
"hash": "D9B305CB6E861D0994A5CDD4726129D91AC4277111DC444DE4CEE44AD4674A9F"
|
||||
}
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"status": "success",
|
||||
"tx_blob": "1200032280000000240000000468400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB88114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json": {
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 4,
|
||||
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType": "AccountSet",
|
||||
"TxnSignature": "3045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB8",
|
||||
"hash": "D9B305CB6E861D0994A5CDD4726129D91AC4277111DC444DE4CEE44AD4674A9F"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```json
|
||||
{
|
||||
"result" : {
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200032280000000240000000468400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB88114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json" : {
|
||||
"Account" : "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 4,
|
||||
"SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "3045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB8",
|
||||
"hash" : "D9B305CB6E861D0994A5CDD4726129D91AC4277111DC444DE4CEE44AD4674A9F"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The `sign` command response contains a `tx_blob` value, as shown above. The offline signing response contains a `signedTransaction` value. Both are signed binary representations (blobs) of the transaction.
|
||||
|
||||
Next, use the `submit` command to send the transaction blob (`tx_blob` or `signedTransaction`) to the network.
|
||||
|
||||
|
||||
### Submit Your Transaction
|
||||
|
||||
Take the `signedTransaction` value from the offline signing response or the `tx_blob` value from the `sign` command response and submit it as the `tx_blob` value using the [submit method][].
|
||||
|
||||
#### Request Format
|
||||
|
||||
An example of the request format:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"command": "submit",
|
||||
"tx_blob": "1200032280000000240000000468400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB88114623B8DA4A0BFB3B61AB423391A182DC693DC159E"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"method":"submit",
|
||||
"params": [
|
||||
{
|
||||
"tx_blob": "1200032280000000240000000468400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB88114623B8DA4A0BFB3B61AB423391A182DC693DC159E"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
#Syntax: submit tx_blob
|
||||
rippled submit 1200032280000000240000000468400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB88114623B8DA4A0BFB3B61AB423391A182DC693DC159E
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
#### Response Format
|
||||
|
||||
An example of a successful response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"tx_blob": "1200032280000000240000000468400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB88114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json": {
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 4,
|
||||
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType": "AccountSet",
|
||||
"TxnSignature": "3045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB8",
|
||||
"hash": "D9B305CB6E861D0994A5CDD4726129D91AC4277111DC444DE4CEE44AD4674A9F"
|
||||
}
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"status": "success",
|
||||
"tx_blob": "1200032280000000240000000468400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB88114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json": {
|
||||
"Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 4,
|
||||
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType": "AccountSet",
|
||||
"TxnSignature": "3045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB8",
|
||||
"hash": "D9B305CB6E861D0994A5CDD4726129D91AC4277111DC444DE4CEE44AD4674A9F"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```json
|
||||
{
|
||||
"result" : {
|
||||
"engine_result" : "tesSUCCESS",
|
||||
"engine_result_code" : 0,
|
||||
"engine_result_message" : "The transaction was applied. Only final in a validated ledger.",
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200032280000000240000000468400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB88114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json" : {
|
||||
"Account" : "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 4,
|
||||
"SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "3045022100A50E867D3B1B5A39F23F1ABCA5C7C3EC755442FDAA357EFD897B865ACA7686DB02206077BF459BCE39BCCBFE1A128DA986D1E00CBEC5F0D6B0E11710F60BE2976FB8",
|
||||
"hash" : "D9B305CB6E861D0994A5CDD4726129D91AC4277111DC444DE4CEE44AD4674A9F"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
If the transaction fails with the following [result codes](../../../references/protocol/transactions/transaction-results/transaction-results.md), here are some things to check:
|
||||
|
||||
- **`tefBAD_AUTH`**: The regular key you signed your test transaction with doesn't match the regular key you set in the previous step. Check that the secret and address for your regular key pair match and double-check which values you used in each step.
|
||||
- **`tefBAD_AUTH_MASTER`** or **`temBAD_AUTH_MASTER`**: Your account doesn't have a regular key assigned. Check that the SetRegularKey transaction executed successfully. You can also use the [account_info method][] to confirm that your regular key is set in the `RegularKey` field as expected.
|
||||
|
||||
For possible causes of other result codes, see [Transaction Results](../../../references/protocol/transactions/transaction-results/transaction-results.md).
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
Now that you're familiar with the benefits of assigning a regular key pair to an account, consider taking a look at these related topics and tutorials:
|
||||
|
||||
- **Concepts:**
|
||||
- [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md)
|
||||
- [Multi-Signing](../../../concepts/accounts/multi-signing.md)
|
||||
- [Issuing and Operational Addresses](../../../concepts/accounts/account-types.md)
|
||||
- **Tutorials:**
|
||||
- [Change or Remove a Regular Key Pair](change-or-remove-a-regular-key-pair.md)
|
||||
- [Set Up Multi-Signing](set-up-multi-signing.md)
|
||||
- [List XRP as an Exchange](../../../use-cases/defi/list-xrp-as-an-exchange.md)
|
||||
- **References:**
|
||||
- [wallet_propose method][]
|
||||
- [sign method][]
|
||||
- [SetRegularKey transaction][]
|
||||
- [AccountRoot object](../../../references/protocol/ledger-data/ledger-entry-types/accountroot.md) where the regular key is stored in the field `RegularKey`
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,382 @@
|
||||
---
|
||||
html: change-or-remove-a-regular-key-pair.html
|
||||
parent: manage-account-settings.html
|
||||
seo:
|
||||
description: Remove or update a regular key pair already authorized by your account.
|
||||
labels:
|
||||
- Security
|
||||
- Accounts
|
||||
---
|
||||
# Change or Remove a Regular Key Pair
|
||||
|
||||
The XRP Ledger allows an account to authorize a secondary key pair, called a _[regular key pair](../../../concepts/accounts/cryptographic-keys.md)_, to sign future transactions. If your [account](../../../concepts/accounts/accounts.md)'s regular key pair is compromised, or if you want to periodically change the regular key pair as a security measure, use a [SetRegularKey transaction][] to remove or change the regular key pair for your account.
|
||||
|
||||
For more information about master and regular key pairs, see [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md).
|
||||
|
||||
|
||||
## Changing a Regular Key Pair
|
||||
|
||||
The steps to change your existing regular key pair are almost the same as the steps to [assign a regular key](assign-a-regular-key-pair.md) for the first time. You generate the key pair and assign it to your account as a regular key pair, overwriting the existing regular key pair. However, the main difference is that when changing the existing regular key pair, you can use the existing regular private key to replace itself; but when assigning a regular key pair to an account for the first time, you have to use the account's master private key to do it.
|
||||
|
||||
For more information about master and regular key pairs, see [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md).
|
||||
|
||||
|
||||
## Removing a Regular Key Pair
|
||||
|
||||
If you want to remove a compromised regular key pair from your account, you don't need to generate a key pair first. Use a [SetRegularKey transaction][], omitting the `RegularKey` field. Note that the transaction fails if you don't have another way of signing for your account currently enabled (either the master key pair or a [signer list](../../../concepts/accounts/multi-signing.md)).
|
||||
|
||||
|
||||
When removing a regular key pair to your account, the `SetRegularKey` transaction requires signing by your account's master private key (secret) or existing regular key pair. Sending your master or regular private key anywhere is dangerous, so we keep transaction signing separate from transaction submission to the network.
|
||||
|
||||
### Sign Your Transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-sign-step.md" /%}
|
||||
|
||||
|
||||
Populate the request fields with the following values:
|
||||
|
||||
| Request Field | Value |
|
||||
|:--------------|:-------------------------------------------------------------|
|
||||
| `Account` | The address of your account. |
|
||||
| `secret` | `master_key`, `master_seed`, or `master_seed_hex` (master or regular private key) for your account. |
|
||||
|
||||
|
||||
#### Request Format
|
||||
|
||||
An example of the request format:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"command": "sign",
|
||||
"tx_json": {
|
||||
"TransactionType": "SetRegularKey",
|
||||
"Account": "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8"
|
||||
},
|
||||
"secret": "snoPBrXtMeMyMHUVTgbuqAfg1SUTb"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"method": "sign",
|
||||
"params": [
|
||||
{
|
||||
"secret" : "snoPBrXtMeMyMHUVTgbuqAfg1SUTb",
|
||||
"tx_json" : {
|
||||
"TransactionType" : "SetRegularKey",
|
||||
"Account" : "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
#Syntax: sign secret tx_json
|
||||
rippled sign snoPBrXtMeMyMHUVTgbuqAfg1SUTb '{"TransactionType": "SetRegularKey", "Account": "rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93"}'
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
#### Response Format
|
||||
|
||||
An example of a successful response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"tx_blob": "1200052280000000240000000268400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E838114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json": {
|
||||
"Account": "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 2,
|
||||
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType": "SetRegularKey",
|
||||
"TxnSignature": "3045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E83",
|
||||
"hash": "59BCAB8E5B9D4597D6A7BFF22F6C555D0F41420599A2E126035B6AF19261AD97"
|
||||
}
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"status": "success",
|
||||
"tx_blob": "1200052280000000240000000268400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E838114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json": {
|
||||
"Account": "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 2,
|
||||
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType": "SetRegularKey",
|
||||
"TxnSignature": "3045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E83",
|
||||
"hash": "59BCAB8E5B9D4597D6A7BFF22F6C555D0F41420599A2E126035B6AF19261AD97"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```json
|
||||
{
|
||||
"result" : {
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200052280000000240000000268400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E838114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json" : {
|
||||
"Account" : "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 2,
|
||||
"SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType" : "SetRegularKey",
|
||||
"TxnSignature" : "3045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E83",
|
||||
"hash" : "59BCAB8E5B9D4597D6A7BFF22F6C555D0F41420599A2E126035B6AF19261AD97"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The `sign` command response contains a `tx_blob` value, as shown above. The offline signing response contains a `signedTransaction` value. Both are signed binary representations (blobs) of the transaction.
|
||||
|
||||
Next, use the `submit` command to send the transaction blob (`tx_blob` or `signedTransaction`) to the network.
|
||||
|
||||
|
||||
### Submit Your Transaction
|
||||
|
||||
Take the `signedTransaction` value from the offline signing response or the `tx_blob` value from the `sign` command response and submit it as the `tx_blob` value using the [submit method][].
|
||||
|
||||
#### Request Format
|
||||
|
||||
An example of the request format:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"command": "submit",
|
||||
"tx_blob": "1200052280000000240000000268400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E838114623B8DA4A0BFB3B61AB423391A182DC693DC159E"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"method":"submit",
|
||||
"params":[
|
||||
{
|
||||
"tx_blob":"1200052280000000240000000268400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E838114623B8DA4A0BFB3B61AB423391A182DC693DC159E"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
#Syntax: submit tx_blob
|
||||
rippled submit 1200052280000000240000000268400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E838114623B8DA4A0BFB3B61AB423391A182DC693DC159E
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
#### Response Format
|
||||
|
||||
An example of a successful response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"tx_blob": "1200052280000000240000000268400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E838114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json": {
|
||||
"Account": "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 2,
|
||||
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType": "SetRegularKey",
|
||||
"TxnSignature": "3045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E83",
|
||||
"hash": "59BCAB8E5B9D4597D6A7BFF22F6C555D0F41420599A2E126035B6AF19261AD97"
|
||||
}
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"status": "success",
|
||||
"tx_blob": "1200052280000000240000000268400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E838114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json": {
|
||||
"Account": "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 2,
|
||||
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType": "SetRegularKey",
|
||||
"TxnSignature": "3045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E83",
|
||||
"hash": "59BCAB8E5B9D4597D6A7BFF22F6C555D0F41420599A2E126035B6AF19261AD97"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```json
|
||||
{
|
||||
"result" : {
|
||||
"engine_result" : "tesSUCCESS",
|
||||
"engine_result_code" : 0,
|
||||
"engine_result_message" : "The transaction was applied. Only final in a validated ledger.",
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200052280000000240000000268400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E838114623B8DA4A0BFB3B61AB423391A182DC693DC159E",
|
||||
"tx_json" : {
|
||||
"Account" : "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 2,
|
||||
"SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType" : "SetRegularKey",
|
||||
"TxnSignature" : "3045022100CAB9A6F84026D57B05760D5E2395FB7BE86BF39F10DC6E2E69DC91238EE0970B022058EC36A8EF9EE65F5D0D8CAC4E88C8C19FEF39E40F53D4CCECBB59701D6D1E83",
|
||||
"hash" : "59BCAB8E5B9D4597D6A7BFF22F6C555D0F41420599A2E126035B6AF19261AD97"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The way to verify that regular key pair removal succeeded is to confirm that you can't send a transaction using the removed regular private key.
|
||||
|
||||
Here's an example error response for an [AccountSet transaction][] signed using the regular private key removed by the `SetRegularKey` transaction above.
|
||||
|
||||
|
||||
### Response Format
|
||||
|
||||
An example of a successful response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"error": "badSecret",
|
||||
"error_code": 41,
|
||||
"error_message": "Secret does not match account.",
|
||||
"request": {
|
||||
"command": "submit",
|
||||
"secret": "snoPBrXtMeMyMHUVTgbuqAfg1SUTb",
|
||||
"tx_json": {
|
||||
"Account": "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8",
|
||||
"TransactionType": "AccountSet"
|
||||
}
|
||||
},
|
||||
"status": "error",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"error": "badSecret",
|
||||
"error_code": 41,
|
||||
"error_message": "Secret does not match account.",
|
||||
"request": {
|
||||
"command": "submit",
|
||||
"secret": "snoPBrXtMeMyMHUVTgbuqAfg1SUTb",
|
||||
"tx_json": {
|
||||
"Account": "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8",
|
||||
"TransactionType": "AccountSet"
|
||||
}
|
||||
},
|
||||
"status": "error"
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```json
|
||||
{
|
||||
"result" : {
|
||||
"error" : "badSecret",
|
||||
"error_code" : 41,
|
||||
"error_message" : "Secret does not match account.",
|
||||
"request" : {
|
||||
"command" : "submit",
|
||||
"secret" : "snoPBrXtMeMyMHUVTgbuqAfg1SUTb",
|
||||
"tx_json" : {
|
||||
"Account" : "r9xQZdFGwbwTB3g9ncKByWZ3du6Skm7gQ8",
|
||||
"TransactionType" : "AccountSet"
|
||||
}
|
||||
},
|
||||
"status" : "error"
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
In some cases, you can even use the `SetRegularKey` transaction to send a [key reset transaction](../../../concepts/transactions/transaction-cost.md#key-reset-transaction) without paying the [transaction cost](../../../concepts/transactions/transaction-cost.md). The XRP Ledger's [transaction queue](../../../concepts/transactions/transaction-queue.md) prioritizes key reset transactions above other transactions even though the nominal transaction cost of a key reset transaction is zero.
|
||||
|
||||
|
||||
- **Concepts:**
|
||||
- [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md)
|
||||
- [Multi-Signing](../../../concepts/accounts/multi-signing.md)
|
||||
- [Transaction Cost](../../../concepts/transactions/transaction-cost.md)
|
||||
- **Tutorials:**
|
||||
- [Change or Remove a Regular Key Pair](change-or-remove-a-regular-key-pair.md)
|
||||
- [Set Up Multi-Signing](set-up-multi-signing.md)
|
||||
- [List XRP as an Exchange](../../../use-cases/defi/list-xrp-as-an-exchange.md)
|
||||
- **References:**
|
||||
- [wallet_propose method][]
|
||||
- [sign method][]
|
||||
- [SetRegularKey transaction][]
|
||||
- [AccountRoot object](../../../references/protocol/ledger-data/ledger-entry-types/accountroot.md) where the regular key is stored in the field `RegularKey`
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,502 @@
|
||||
---
|
||||
html: disable-master-key-pair.html
|
||||
parent: manage-account-settings.html
|
||||
seo:
|
||||
description: Disable the master key that is mathematically associated with an address.
|
||||
labels:
|
||||
- Security
|
||||
- Accounts
|
||||
---
|
||||
# Disable Master Key Pair
|
||||
|
||||
This page describes how to disable the [master key pair](../../../concepts/accounts/cryptographic-keys.md) that is mathematically associated with an [account](../../../concepts/accounts/accounts.md)'s address. You should do this if your account's master key pair may have been compromised, or if you want to make [multi-signing](../../../concepts/accounts/multi-signing.md) the _only_ way to submit transactions from your account.
|
||||
|
||||
**Warning:** Disabling the master key pair removes one method of [authorizing transactions](../../../concepts/transactions/index.md#authorizing-transactions). You should be sure you can use one of the other ways of authorizing transactions, such as with a regular key or by multi-signing, before you disable the master key pair. (For example, if you [assigned a regular key pair](assign-a-regular-key-pair.md), make sure that you can successfully submit transactions with that regular key.) Due to the decentralized nature of the XRP Ledger, no one can restore access to your account if you cannot use the remaining ways of authorizing transactions.
|
||||
|
||||
**To disable the master key pair, you must use the master key pair.** However, you can _re-enable_ the master key pair using any other method of authorizing transactions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To disable the master key pair for an account, you must meet the following prerequisites:
|
||||
|
||||
- You must have an XRP Ledger [account](../../../concepts/accounts/accounts.md) and you must be able to sign and submit transactions from that account using the master key pair. See also: [Set Up Secure Signing](../../../concepts/transactions/secure-signing.md). Two common ways this can work are:
|
||||
- You know the account's master seed value. A seed value is commonly represented as a [base58][] value starting with "s", such as `sn3nxiW7v8KXzPzAqzyHXbSSKNuN9`.
|
||||
- Or, you use a [dedicated signing device](../../../concepts/transactions/secure-signing.md#use-a-dedicated-signing-device) that stores the seed value securely, so you don't need to know it.
|
||||
- Your account must have at least one method of authorizing transactions other than the master key pair. In other words, you must do one or both of the following:
|
||||
- [Assign a Regular Key Pair](assign-a-regular-key-pair.md).
|
||||
- [Set Up Multi-Signing](set-up-multi-signing.md).
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Construct Transaction JSON
|
||||
|
||||
Prepare an [AccountSet transaction][] from your account with the field `"SetValue": 4`. This is the value for the AccountSet flag "Disable Master" (`asfDisableMaster`). The only other required fields for this transaction are the required [common fields](../../../references/protocol/transactions/common-fields.md). For example, if you leave off the [auto-fillable fields](../../../references/protocol/transactions/common-fields.md#auto-fillable-fields), the following transaction instructions are enough:
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"SetFlag": 4
|
||||
}
|
||||
```
|
||||
|
||||
**Tip:** It is strongly recommended to also provide the `LastLedgerSequence` field so that you can [reliably get the outcome of the transaction in a predictable amount of time](../../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
### 2. Sign Transaction
|
||||
|
||||
You must use the **master key pair** to sign the transaction.
|
||||
|
||||
**Warning:** Do not submit your secret to a server you don't control, and do not send it over the network unencrypted. These examples assume you are using a [local `rippled` server](../../../concepts/transactions/secure-signing.md#run-rippled-locally). You should adapt these instructions if you are using another [secure signing configuration](../../../concepts/transactions/secure-signing.md).
|
||||
|
||||
#### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"command": "sign",
|
||||
"tx_json": {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"SetFlag": 4
|
||||
},
|
||||
"secret": "s████████████████████████████"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"method": "sign",
|
||||
"params": [
|
||||
{
|
||||
"tx_json": {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"SetFlag": 4
|
||||
},
|
||||
"secret": "s████████████████████████████"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
$ rippled sign s████████████████████████████ '{"TransactionType":"AccountSet",
|
||||
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "SetFlag":4}'
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
#### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"deprecated": "This command has been deprecated and will be removed in a future version of the server. Please migrate to a standalone signing tool.",
|
||||
"tx_blob": "1200032280000000240000017C20210000000468400000000000000A732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
|
||||
"tx_json": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 380,
|
||||
"SetFlag": 4,
|
||||
"SigningPubKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
|
||||
"TransactionType": "AccountSet",
|
||||
"TxnSignature": "304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D",
|
||||
"hash": "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70"
|
||||
}
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"deprecated": "This command has been deprecated and will be removed in a future version of the server. Please migrate to a standalone signing tool.",
|
||||
"status": "success",
|
||||
"tx_blob": "1200032280000000240000017C20210000000468400000000000000A732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
|
||||
"tx_json": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 380,
|
||||
"SetFlag": 4,
|
||||
"SigningPubKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
|
||||
"TransactionType": "AccountSet",
|
||||
"TxnSignature": "304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D",
|
||||
"hash": "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
2020-Feb-13 00:13:24.783570867 HTTPClient:NFO Connecting to 127.0.0.1:5005
|
||||
|
||||
{
|
||||
"result" : {
|
||||
"deprecated" : "This command has been deprecated and will be removed in a future version of the server. Please migrate to a standalone signing tool.",
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200032280000000240000017C20210000000468400000000000000A732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
|
||||
"tx_json" : {
|
||||
"Account" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 380,
|
||||
"SetFlag" : 4,
|
||||
"SigningPubKey" : "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D",
|
||||
"hash" : "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Look for `"status": "success"` to indicate that the server successfully signed the transaction. If you get `"status": "error"` instead, check the `error` and `error_message` fields for more information. Some common possibilities include:
|
||||
|
||||
- `"error": "badSecret"` usually means you made a typo in the `secret` of the request.
|
||||
- `"error": "masterDisabled"` means this address's master key pair is _already_ disabled.
|
||||
|
||||
Take note of the `tx_blob` value from the response. This is a signed transaction binary you can submit to the network.
|
||||
|
||||
### 3. Submit Transaction
|
||||
|
||||
Submit the signed transaction blob from the previous step to the XRP Ledger.
|
||||
|
||||
#### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"command": "submit",
|
||||
"tx_blob": "1200032280000000240000017C20210000000468400000000000000A732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"method":"submit",
|
||||
"params": [
|
||||
{
|
||||
"tx_blob": "1200032280000000240000017C20210000000468400000000000000A732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```
|
||||
$ rippled submit 1200032280000000240000017C20210000000468400000000000000A732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
#### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"engine_result" : "tesSUCCESS",
|
||||
"engine_result_code" : 0,
|
||||
"engine_result_message" : "The transaction was applied. Only final in a validated ledger.",
|
||||
"tx_blob" : "1200032280000000240000017C20210000000468400000000000000A732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
|
||||
"tx_json" : {
|
||||
"Account" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 380,
|
||||
"SetFlag" : 4,
|
||||
"SigningPubKey" : "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D",
|
||||
"hash" : "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70"
|
||||
}
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"result" : {
|
||||
"engine_result" : "tesSUCCESS",
|
||||
"engine_result_code" : 0,
|
||||
"engine_result_message" : "The transaction was applied. Only final in a validated ledger.",
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200032280000000240000017C20210000000468400000000000000A732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
|
||||
"tx_json" : {
|
||||
"Account" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 380,
|
||||
"SetFlag" : 4,
|
||||
"SigningPubKey" : "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D",
|
||||
"hash" : "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
2020-Feb-13 00:25:49.361743460 HTTPClient:NFO Connecting to 127.0.0.1:5005
|
||||
|
||||
{
|
||||
"result" : {
|
||||
"engine_result" : "tesSUCCESS",
|
||||
"engine_result_code" : 0,
|
||||
"engine_result_message" : "The transaction was applied. Only final in a validated ledger.",
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200032280000000240000017C20210000000468400000000000000A732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
|
||||
"tx_json" : {
|
||||
"Account" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 380,
|
||||
"SetFlag" : 4,
|
||||
"SigningPubKey" : "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "304402204457A890BC06F48061F8D61042975702B57EBEF3EA2C7C484DFE38CFD42EA11102202505A7C62FF41E68FDE10271BADD75BD66D54B2F96A326BE487A2728A352442D",
|
||||
"hash" : "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
If the transaction fails with the result `tecNO_ALTERNATIVE_KEY`, your account does not have another method of authorizing transactions currently enabled. You must [assign a regular key pair](assign-a-regular-key-pair.md) or [set up multi-signing](set-up-multi-signing.md), then try again to disable the master key pair.
|
||||
|
||||
|
||||
### 4. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
### 5. Confirm Account Flags
|
||||
|
||||
Confirm that your account's master key is disabled using the [account_info method][]. Be sure to specify the following parameters:
|
||||
|
||||
| Field | Value |
|
||||
|:---------------|:------------------------------------------------------------|
|
||||
| `account` | The address of your account. |
|
||||
| `ledger_index` | `"validated"` to get results from the latest validated ledger version. |
|
||||
|
||||
#### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"command": "account_info",
|
||||
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"ledger_index": "validated"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"method": "account_info",
|
||||
"params": [{
|
||||
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"ledger_index": "validated"
|
||||
}]
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
rippled account_info rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn validated
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
#### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"account_data": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"AccountTxnID": "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70",
|
||||
"Balance": "423013688",
|
||||
"Domain": "6D64756F31332E636F6D",
|
||||
"EmailHash": "98B4375E1D753E5B91627516F6D70977",
|
||||
"Flags": 9633792,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"MessageKey": "0000000000000000000000070000000300",
|
||||
"OwnerCount": 9,
|
||||
"PreviousTxnID": "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70",
|
||||
"PreviousTxnLgrSeq": 53391321,
|
||||
"RegularKey": "rD9iJmieYHn8jTtPjwwkW2Wm9sVDvPXLoJ",
|
||||
"Sequence": 381,
|
||||
"TransferRate": 4294967295,
|
||||
"index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
|
||||
"urlgravatar": "http://www.gravatar.com/avatar/98b4375e1d753e5b91627516f6d70977"
|
||||
},
|
||||
"ledger_hash": "A90CEBD4AEDA24470AAC5CD307B6D26267ACE79C03669A0A0B8C41ACAEDAA6F0",
|
||||
"ledger_index": 53391576,
|
||||
"validated": true
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"account_data": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"AccountTxnID": "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70",
|
||||
"Balance": "423013688",
|
||||
"Domain": "6D64756F31332E636F6D",
|
||||
"EmailHash": "98B4375E1D753E5B91627516F6D70977",
|
||||
"Flags": 9633792,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"MessageKey": "0000000000000000000000070000000300",
|
||||
"OwnerCount": 9,
|
||||
"PreviousTxnID": "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70",
|
||||
"PreviousTxnLgrSeq": 53391321,
|
||||
"RegularKey": "rD9iJmieYHn8jTtPjwwkW2Wm9sVDvPXLoJ",
|
||||
"Sequence": 381,
|
||||
"TransferRate": 4294967295,
|
||||
"index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
|
||||
"urlgravatar": "http://www.gravatar.com/avatar/98b4375e1d753e5b91627516f6d70977"
|
||||
},
|
||||
"ledger_hash": "4C4AC95149B13B539369998675FE6860C52695E83658366F18872181C9F1AEBF",
|
||||
"ledger_index": 53391589,
|
||||
"status": "success",
|
||||
"validated": true
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
2020-Feb-13 00:41:38.642710734 HTTPClient:NFO Connecting to 127.0.0.1:5005
|
||||
|
||||
{
|
||||
"result" : {
|
||||
"account_data" : {
|
||||
"Account" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"AccountTxnID" : "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70",
|
||||
"Balance" : "423013688",
|
||||
"Domain" : "6D64756F31332E636F6D",
|
||||
"EmailHash" : "98B4375E1D753E5B91627516F6D70977",
|
||||
"Flags" : 9633792,
|
||||
"LedgerEntryType" : "AccountRoot",
|
||||
"MessageKey" : "0000000000000000000000070000000300",
|
||||
"OwnerCount" : 9,
|
||||
"PreviousTxnID" : "327FD263132A4D08170E1B01FE1BB2E21D0126CE58165C97A9173CA9551BCD70",
|
||||
"PreviousTxnLgrSeq" : 53391321,
|
||||
"RegularKey" : "rD9iJmieYHn8jTtPjwwkW2Wm9sVDvPXLoJ",
|
||||
"Sequence" : 381,
|
||||
"TransferRate" : 4294967295,
|
||||
"index" : "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
|
||||
"urlgravatar" : "http://www.gravatar.com/avatar/98b4375e1d753e5b91627516f6d70977"
|
||||
},
|
||||
"ledger_hash" : "BBA4034FB5D5D89987E0987A9491E7B62B16708EECFF04CDB0367BD4D28EB1B5",
|
||||
"ledger_index" : 53391568,
|
||||
"status" : "success",
|
||||
"validated" : true
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
In the response's `account_data` object, compare the `Flags` field with the `lsfDisableMaster` flag value (`0x00100000` in hex, or `1048576` in decimal) using bitwise-AND (the `&` operator in most common programming languages).
|
||||
|
||||
Example code:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
// Assuming the JSON-RPC response above is saved as account_info_response
|
||||
const lsfDisableMaster = 0x00100000;
|
||||
let acct_flags = account_info_response.result.account_data.Flags;
|
||||
if ((lsfDisableMaster & acct_flags) === lsfDisableMaster) {
|
||||
console.log("Master key pair is DISABLED");
|
||||
} else {
|
||||
console.log("Master key pair is available for use");
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
```python
|
||||
# Assuming the JSON-RPC response above is parsed from JSON
|
||||
# and saved as the variable account_info_response
|
||||
lsfDisableMaster = 0x00100000
|
||||
acct_flags = account_info_response["result"]["account_data"]["Flags"]
|
||||
if lsfDisableMaster & acct_flags == lsfDisableMaster:
|
||||
print("Master key pair is DISABLED")
|
||||
else:
|
||||
print("Master key pair is available for use")
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
This operation has only two possible outcomes:
|
||||
|
||||
- A nonzero result, equal to the `lsfDisableMaster` value, indicates **the master key has been successfully disabled**.
|
||||
- A zero result indicates the account's master key is not disabled.
|
||||
|
||||
If the result does not match your expectations, check whether the transaction you sent in the previous steps has executed successfully. It should be the most recent entry in the account's transaction history ([account_tx method][]) and it should have the result code `tesSUCCESS`. If you see any other [result code](../../../references/protocol/transactions/transaction-results/transaction-results.md), the transaction was not executed successfully. Depending on the cause of the error, you may want to restart these steps from the beginning.
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,345 @@
|
||||
---
|
||||
html: offline-account-setup.html
|
||||
parent: manage-account-settings.html
|
||||
seo:
|
||||
description: Set up an XRP Ledger account using an air-gapped, offline machine to store its cryptographic keys.
|
||||
labels:
|
||||
- Accounts
|
||||
- Security
|
||||
---
|
||||
# Offline Account Setup Tutorial
|
||||
|
||||
A highly secure [signing configuration](../../../concepts/transactions/secure-signing.md) involves keeping an XRP Ledger [account](../../../concepts/accounts/accounts.md)'s [cryptographic keys](../../../concepts/accounts/cryptographic-keys.md) securely on an offline, air-gapped machine. After setting up this configuration, you can sign a variety of transactions, transfer only the signed transactions to an online computer, and submit them to the XRP Ledger network without ever exposing your secret key to malicious actors online.
|
||||
|
||||
**Caution:** Proper operational security is necessary to protect your offline machine. For example, the offline machine must be physically located where untrusted people cannot get access to it, and trusted operators must be careful not to transfer compromised software onto the machine. (For example, do not use a USB drive that was previously attached to a network-connected computer.)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To use offline signing, you must meet the following prerequisites:
|
||||
|
||||
- You must have one computer to use as an offline machine. This machine must be set up with a [supported operating system](../../../infrastructure/installation/system-requirements.md). See your operating system's support for offline setup instructions. (For example, [Red Hat Enterprise Linux DVD ISO installation instructions](https://access.redhat.com/solutions/7227).) Be sure that the software and physical media you use are not infected with malware.
|
||||
- You must have a separate computer to use as an online machine. This machine does not need to run `rippled` but it must be able to connect to the XRP Ledger network and receive information about the state of the shared ledger. For example, you can use a [WebSocket connection to a public server](../../http-websocket-apis/get-started.md).
|
||||
- You must have a secure way to transfer signed transaction binary data from the offline machine to the online machine.
|
||||
- One way to do this is with a QR code generator on the offline machine, and a QR code scanner on the online machine. (In this case, your "online machine" could be a handheld device such as a smartphone.)
|
||||
- Another way is to copy files from the offline machine to an online machine using physical media. If you use this method, be sure not to use physical media that could infect your offline machine with malicious software. (For example, do not reuse the same USB drive on both online and offline machines.)
|
||||
- You _could_ manually type the data onto the online machine, but doing so would be tedious and error-prone.
|
||||
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Set up offline machine
|
||||
|
||||
The offline machine needs secure persistent storage (for example, an encrypted disk drive) and a way to [sign transactions](../../../concepts/transactions/secure-signing.md). For an offline machine, you typically use physical media to transfer any necessary software after downloading it from an online machine. You must be sure that the online machine, the physical media, and the software itself are not infected with malware.
|
||||
|
||||
Software options for signing on the XRP Ledger include:
|
||||
|
||||
- [Install `rippled`](../../../infrastructure/installation/index.md) from a package (`.deb` or `.rpm` depending on which Linux distribution you use) file, then [run it in stand-alone mode](../../../concepts/networks-and-servers/rippled-server-modes.md).
|
||||
- Install [xrpl.js](https://github.com/XRPLF/xrpl.js/) (or another [client library](../../../references/client-libraries.md)) and its dependencies offline. The Yarn package manager, for example, has [recommended instructions for offline usage](https://yarnpkg.com/blog/2016/11/24/offline-mirror/).
|
||||
- See also: [Set Up Secure Signing](../../../concepts/transactions/secure-signing.md)
|
||||
|
||||
You may want to set up custom software to help construct transaction instructions on the offline machine. For example, your software may track what [sequence number][] to use next, or contain preset templates for certain types of transactions you expect to send.
|
||||
|
||||
|
||||
### 2. Generate cryptographic keys
|
||||
|
||||
On the **offline machine**, generate a pair of [cryptographic keys](../../../concepts/accounts/cryptographic-keys.md) to be used with your account. Be sure to generate the keys with a securely random procedure, not from a short passphrase or some other source that does not have enough entropy. For example, you can use the [wallet_propose method][] of `rippled`:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="rippled Commandline" %}
|
||||
```sh
|
||||
$ ./rippled wallet_propose
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
2019-Dec-09 22:58:24.110862955 HTTPClient:NFO Connecting to 127.0.0.1:5005
|
||||
|
||||
{
|
||||
"result" : {
|
||||
"account_id" : "r4MRc4BArFPXmiDjmLdrufyFManSYhfKE6",
|
||||
"key_type" : "secp256k1",
|
||||
"master_key" : "JANE GIBE LIST TEND NU RUDE JIG PA FLOG DEFT SAME NASH",
|
||||
"master_seed" : "shYHSiJod8CLPTj1SNJ2PdUFj4pFk",
|
||||
"master_seed_hex" : "8465FDB80B2E2620A7D58274C26291A0",
|
||||
"public_key" : "aBQLW8imt7VChRJU1NMVCB7fE3jSL3VNEgLDKf88ygAhnfuZh3oo",
|
||||
"public_key_hex" : "03396074ED4B8155ACF9A8DC3665EFA53B5CFA0A1E91C3879303D37721EB222644",
|
||||
"status" : "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Take note of the following values:
|
||||
|
||||
- **`account_id`**. This is the address associated with the key pair, which becomes your **[account](../../../concepts/accounts/accounts.md) address** in the XRP Ledger after you fund it with XRP (later in this process). It is safe to share your `account_id` publicly.
|
||||
- **`master_seed`**. This is the secret seed value for the key pair, which you'll use to sign transactions from the account. For best security, encrypt this value before writing it to disk on the offline machine. As an encryption key, use a secure passphrase that human operators can memorize or write down somewhere physically secure, such as a [diceware passphrase](https://theworld.com/~reinhold/diceware.html) created with properly weighted dice. You may also want to use a physical security key as a second factor. The extent of the precautions to take at this stage is up to you.
|
||||
- **`key_type`**. This is the cryptographic algorithm used for this key pair. You need to know what type of key pair you have. The default in `rippled` is `secp256k1`, but some client libraries use `Ed25519` by default.
|
||||
|
||||
**Do not** share the `master_key`, `master_seed`, or `master_seed_hex` values anywhere. Any of these can be used to reconstruct the private key associated with this address.
|
||||
|
||||
<!-- SPELLING_IGNORE: diceware -->
|
||||
|
||||
|
||||
|
||||
### 3. Fund the new address
|
||||
|
||||
From an online machine, send enough XRP to the **account address** you noted in step 1. For more information, see [Creating Accounts](../../../concepts/accounts/accounts.md#creating-accounts).
|
||||
|
||||
**Tip:** For testing purposes, you can use the [Testnet Faucet](/resources/dev-tools/xrp-faucets) to get a new account with Test XRP, then use that account to fund the address you generated offline.
|
||||
|
||||
|
||||
|
||||
### 4. Confirm account details
|
||||
|
||||
When the transaction from the previous step is validated by consensus, your account has been created. From the online machine, you can confirm the status of the account with the [account_info method][]. Make sure the response contains `"validated": true` to confirm that this result is final.
|
||||
|
||||
Take note of the sequence number of the account, in the `Sequence` field of the result's `account_data`. You need to know the sequence number to sign transactions from the account in future steps.
|
||||
|
||||
The `Sequence` number of a newly-funded account matches the [ledger index][] when it was funded. Before the [DeletableAccounts amendment](/resources/known-amendments.md#deletableaccounts), a newly funded account's `Sequence` number was always 1.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="rippled Commandline" %}
|
||||
```sh
|
||||
$ ./rippled account_info rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn
|
||||
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
2019-Dec-11 01:06:21.728637950 HTTPClient:NFO Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result" : {
|
||||
"account_data" : {
|
||||
"Account" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Balance" : "5000000000000",
|
||||
"Flags" : 0,
|
||||
"LedgerEntryType" : "AccountRoot",
|
||||
"OwnerCount" : 0,
|
||||
"PreviousTxnID" : "00C5B713B11DA775C6F932D38CE162C16FA88B7269BAFC6FDF4C6ADB74419670",
|
||||
"PreviousTxnLgrSeq" : 3,
|
||||
"Sequence" : 1,
|
||||
"index" : "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8"
|
||||
},
|
||||
"ledger_current_index" : 4,
|
||||
"status" : "success",
|
||||
"validated" : false
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Enter the sequence number on the offline machine.
|
||||
|
||||
Save the account's starting sequence number on the offline machine. Whenever you prepare a transaction using the offline machine, use the saved sequence number, then increase the sequence number by 1 and save the new value.
|
||||
|
||||
You can prepare several transactions in advance this way, then transfer the signed transactions to the online machine all at once and submit them. As long as each transaction is validly formed and pays a high enough [transaction cost](../../../concepts/transactions/transaction-cost.md), the XRP Ledger network should eventually include those transactions in validated ledgers, keeping the account's sequence number in the shared XRP Ledger in sync with the "current" sequence number you are tracking on the offline machine. (Most transactions get a final, validated result within 15 seconds or less after being submitted to the network.)
|
||||
|
||||
Optionally, save the current ledger index to the offline machine. You can use this value to choose an appropriate `LastLedgerSequence` value for upcoming transactions.
|
||||
|
||||
|
||||
|
||||
### 6. Sign initial setup transactions, if any.
|
||||
|
||||
On the offline machine, prepare and sign transactions for configuring your account. The details depend on how you intend to use your account. Some examples of things you might want to do include:
|
||||
|
||||
- [Assign a regular key pair](assign-a-regular-key-pair.md) that you can rotate regularly.
|
||||
- [Require destination tags](require-destination-tags.md) so that users can't send you payments without tagging the reason they sent it or the customer it's intended for.
|
||||
- [Set Up Multi-Signing](set-up-multi-signing.md) for a higher bar of account security.
|
||||
- [Enable DepositAuth](../../../concepts/accounts/depositauth.md) so you can only receive payments you've explicitly accepted or from parties you've pre-approved.
|
||||
- [Require Auth](../../../concepts/tokens/fungible-tokens/authorized-trust-lines.md#enabling-require-auth) so that users can't open [trust lines](../../../concepts/tokens/fungible-tokens/index.md) to you without your permission. If you don't plan to use the XRP Ledger's decentralized exchange or [token](../../../concepts/tokens/index.md) features, you may want to do this as a precaution.
|
||||
- [Token Issuers](../../../use-cases/tokenization/stablecoin-issuer.md) may have additional setup, such as:
|
||||
- Set a Transfer Fee for users transferring your tokens.
|
||||
- Disallow XRP payments if you plan to use this address for tokens only.
|
||||
|
||||
At this stage, you are only signing the transactions, not submitting them. For each transaction, you must provide all fields, including fields that are normally auto-fillable such as the `Fee` ([transaction cost](../../../concepts/transactions/transaction-cost.md)) and `Sequence` ([sequence number][]). If you prepare multiple transactions at the same time, you must use sequentially increasing `Sequence` numbers in the order you want the transactions to execute.
|
||||
|
||||
Example (enable Require Auth):
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="rippled Commandline" %}
|
||||
```sh
|
||||
$ rippled sign sn3nxiW7v8KXzPzAqzyHXbSSKNuN9 '{"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Fee": "12", "Sequence": 1, "TransactionType": "AccountSet", "SetFlag": 2}' offline
|
||||
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
2019-Dec-11 00:18:31.865955978 HTTPClient:NFO Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result" : {
|
||||
"deprecated" : "This command has been deprecated and will be removed in a future version of the server. Please migrate to a standalone signing tool.",
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200032280000000240000000120210000000268400000000000000C7321039543A0D3004CDA0904A09FB3710251C652D69EA338589279BC849D47A7B019A174473045022100D5C92D7705036CD7EBB601C8DFCD90927FA591A62AF832C489E9C898EC8E2FA0022052F1819340EB73E9749B8930A6935727362B8E141D1B2E246B49F912223FFD4381144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
|
||||
"tx_json" : {
|
||||
"Account" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee" : "12",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 1,
|
||||
"SetFlag" : 2,
|
||||
"SigningPubKey" : "039543A0D3004CDA0904A09FB3710251C652D69EA338589279BC849D47A7B019A1",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "3045022100D5C92D7705036CD7EBB601C8DFCD90927FA591A62AF832C489E9C898EC8E2FA0022052F1819340EB73E9749B8930A6935727362B8E141D1B2E246B49F912223FFD43",
|
||||
"hash" : "F81C34E7F05423DC1C973CB5008CA41AE984DE142EAA3975A749FABF0D08FA63"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
To ensure _all_ transactions have a final outcome within a limited amount of time, provide a [`LastLedgerSequence`](../../../concepts/transactions/reliable-transaction-submission.md#lastledgersequence) field. This value should be based on the current ledger index (which you must look up from an online machine) and the amount of time you want the transaction to remain valid. Be sure to set a large enough `LastLedgerSequence` value to allow for time spent switching from the online machine to the offline machine and back. For example, a value 256 higher than the current ledger index means that the transaction is valid for about 15 minutes. For more information, see [Finality of Results](../../../concepts/transactions/finality-of-results/index.md) and [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
|
||||
### 7. Copy transactions to online machine.
|
||||
|
||||
After you have signed the transactions, the next step is to get the signed transaction data to your online machine. See [Prerequisites](#prerequisites) for some examples of how to do this.
|
||||
|
||||
|
||||
|
||||
### 8. Submit setup transactions.
|
||||
|
||||
The next step is to submit the transactions. Most transactions should have a final outcome in the next validated ledger after submission (about 4 seconds later), or possibly the ledger after that if they get queued (less than 10 seconds). For detailed steps to track the final outcome of a transaction, see [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
Example of transaction submission:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="rippled Commandline" %}
|
||||
```sh
|
||||
$ rippled submit 1200032280000000240000000120210000000268400000000000000C7321039543A0D3004CDA0904A09FB3710251C652D69EA338589279BC849D47A7B019A174473045022100D5C92D7705036CD7EBB601C8DFCD90927FA591A62AF832C489E9C898EC8E2FA0022052F1819340EB73E9749B8930A6935727362B8E141D1B2E246B49F912223FFD4381144B4E9C06F24296074F7BC48F92A97916C6DC5EA9
|
||||
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
2019-Dec-11 01:14:25.988839227 HTTPClient:NFO Connecting to 127.0.0.1:5005
|
||||
|
||||
{
|
||||
"result" : {
|
||||
"deprecated" : "Signing support in the 'submit' command has been deprecated and will be removed in a future version of the server. Please migrate to a standalone signing tool.",
|
||||
"engine_result" : "tesSUCCESS",
|
||||
"engine_result_code" : 0,
|
||||
"engine_result_message" : "The transaction was applied. Only final in a validated ledger.",
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200032280000000240000000120210000000268400000000000000C7321039543A0D3004CDA0904A09FB3710251C652D69EA338589279BC849D47A7B019A174473045022100D5C92D7705036CD7EBB601C8DFCD90927FA591A62AF832C489E9C898EC8E2FA0022052F1819340EB73E9749B8930A6935727362B8E141D1B2E246B49F912223FFD4381144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
|
||||
"tx_json" : {
|
||||
"Account" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee" : "12",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 1,
|
||||
"SetFlag" : 2,
|
||||
"SigningPubKey" : "039543A0D3004CDA0904A09FB3710251C652D69EA338589279BC849D47A7B019A1",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "3045022100D5C92D7705036CD7EBB601C8DFCD90927FA591A62AF832C489E9C898EC8E2FA0022052F1819340EB73E9749B8930A6935727362B8E141D1B2E246B49F912223FFD43",
|
||||
"hash" : "F81C34E7F05423DC1C973CB5008CA41AE984DE142EAA3975A749FABF0D08FA63"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Tip:** If you are submitting more than 10 transactions at a time, you may have more success if you submit them in groups of 10 or less at a time, because the [transaction queue](../../../concepts/transactions/transaction-queue.md) is limited to 10 transactions from the same sender at a time. After each group of 10 transactions, wait for all the transactions to leave the queue before submitting the next group.
|
||||
|
||||
Retry submitting any transactions that failed with a [non-final outcome](../../../concepts/transactions/finality-of-results/index.md). There is no chance of the same transaction being processed more than once.
|
||||
|
||||
### 9. Confirm the final status of the transactions.
|
||||
|
||||
For each transaction you submitted, note the transaction's [final outcome](../../../concepts/transactions/finality-of-results/index.md), for example using the [tx method][]. For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="rippled Commandline" %}
|
||||
```sh
|
||||
$ ./rippled tx F81C34E7F05423DC1C973CB5008CA41AE984DE142EAA3975A749FABF0D08FA63
|
||||
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
2019-Dec-11 01:38:30.124771464 HTTPClient:NFO Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result" : {
|
||||
"Account" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee" : "12",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 1,
|
||||
"SetFlag" : 2,
|
||||
"SigningPubKey" : "039543A0D3004CDA0904A09FB3710251C652D69EA338589279BC849D47A7B019A1",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "3045022100D5C92D7705036CD7EBB601C8DFCD90927FA591A62AF832C489E9C898EC8E2FA0022052F1819340EB73E9749B8930A6935727362B8E141D1B2E246B49F912223FFD43",
|
||||
"date" : 629343510,
|
||||
"hash" : "F81C34E7F05423DC1C973CB5008CA41AE984DE142EAA3975A749FABF0D08FA63",
|
||||
"inLedger" : 4,
|
||||
"ledger_index" : 4,
|
||||
"meta" : {
|
||||
"AffectedNodes" : [
|
||||
{
|
||||
"ModifiedNode" : {
|
||||
"FinalFields" : {
|
||||
"Account" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Balance" : "4999999999988",
|
||||
"Flags" : 262144,
|
||||
"OwnerCount" : 0,
|
||||
"Sequence" : 2
|
||||
},
|
||||
"LedgerEntryType" : "AccountRoot",
|
||||
"LedgerIndex" : "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
|
||||
"PreviousFields" : {
|
||||
"Balance" : "5000000000000",
|
||||
"Flags" : 0,
|
||||
"Sequence" : 1
|
||||
},
|
||||
"PreviousTxnID" : "00C5B713B11DA775C6F932D38CE162C16FA88B7269BAFC6FDF4C6ADB74419670",
|
||||
"PreviousTxnLgrSeq" : 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"TransactionIndex" : 0,
|
||||
"TransactionResult" : "tesSUCCESS"
|
||||
},
|
||||
"status" : "success",
|
||||
"validated" : true
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
You may also find it useful to check the [account_info][account_info method] of the sending account after all transactions have processed. Note the account's current sequence number (`Sequence` field) and, optionally, XRP balance.
|
||||
|
||||
For any transactions that failed, you should decide what to do:
|
||||
|
||||
- If the transaction failed with a `tefMAX_LEDGER` code, you may need to specify a higher [transaction cost](../../../concepts/transactions/transaction-cost.md) to get the transaction processed. (This likely indicates that the XRP Ledger network is under load.) You may decide to replace the transaction with a new version that pays a higher cost and has a higher `LastLedgerSequence` parameter (if any).
|
||||
- If the transaction failed with any [`tem`-class code](../../../references/protocol/transactions/transaction-results/tem-codes.md), you probably made a typo or another error in constructing the transaction. Double-check the transaction so that you can replace it with a validly-formed one.
|
||||
- If the transaction failed with a [`tec`-class code](../../../references/protocol/transactions/transaction-results/tec-codes.md), you should address it on a case-by-case basis depending on the exact reason it failed.
|
||||
|
||||
For any transactions you decide to adjust or replace, note the details for when you return to the offline machine.
|
||||
|
||||
|
||||
|
||||
### 10. Reconcile offline machine status.
|
||||
|
||||
Return to the offline machine and apply any necessary changes to your custom server's saved settings, such as:
|
||||
|
||||
- Updating the account's current `Sequence` number. If all transactions were included in validated ledgers (successfully or with `tec` codes), then the offline machine's saved sequence number should already be correct. Otherwise, you may need to change the saved sequence number to match the `Sequence` value you noted in the previous step.
|
||||
- Updating the current ledger index so that you can use appropriate `LastLedgerSequence` values in any new transactions. (You should always do this shortly before constructing any new transactions.)
|
||||
- _(Optional)_ Updating your actual amount of XRP available, if you are tracking it in the offline machine.
|
||||
|
||||
Then adjust and sign any replacement transactions for transactions that failed in the previous step. Repeat the previous steps for constructing transactions on the offline machine, transferring them, and submitting them from the online machine.
|
||||
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Accounts](../../../concepts/accounts/accounts.md)
|
||||
- [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md)
|
||||
- **Tutorials:**
|
||||
- [Set Up Secure Signing](../../../concepts/transactions/secure-signing.md)
|
||||
- [Assign a Regular Key Pair](assign-a-regular-key-pair.md)
|
||||
- [Set Up Multi-Signing](set-up-multi-signing.md)
|
||||
- **References:**
|
||||
- [Basic Data Types: Account Sequence](../../../references/protocol/data-types/basic-data-types.md#account-sequence)
|
||||
- [account_info method][]
|
||||
- [sign method][]
|
||||
- [submit method][]
|
||||
- [tx method][]
|
||||
- [AccountSet transaction][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,160 @@
|
||||
---
|
||||
html: require-destination-tags.html
|
||||
parent: manage-account-settings.html
|
||||
seo:
|
||||
description: Require users to specify a destination tag when sending to your address.
|
||||
embed_xrpl_js: true
|
||||
filters:
|
||||
- interactive_steps
|
||||
labels:
|
||||
- Accounts
|
||||
steps: ['Generate', 'Connect', 'Send AccountSet', 'Wait', 'Confirm Settings', 'Test Payments']
|
||||
---
|
||||
# Require Destination Tags
|
||||
|
||||
The Require Destination Tag setting is designed for addresses that host balances for multiple people or purposes, to prevent people from sending money and forgetting to use a [destination tag](../../../concepts/transactions/source-and-destination-tags.md) to identify whom to credit. When this setting is enabled on your address, the XRP Ledger rejects [any payment](../../../concepts/payment-types/index.md) to your address if it does not specify a destination tag.
|
||||
|
||||
This tutorial demonstrates how to enable the Require Destination Tag flag on your account.
|
||||
|
||||
**Note:** The meanings of specific destination tags are entirely up to the logic built on top of the XRP Ledger. The ledger has no way of knowing whether any specific tag is valid in your system, so you must still be ready to receive transactions with the wrong destination tag. Typically, this involves providing a customer support experience that can track down payments made incorrectly and credit customers accordingly, and possibly also bouncing unwanted payments.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You need a funded XRP Ledger account, with an address, secret key, and some XRP. For production, you can use the same address and secret consistently. For this tutorial, you can generate new test credentials as needed.
|
||||
- You need a connection to the XRP Ledger network. As shown in this tutorial, you can use public servers for testing.
|
||||
- You should be familiar with the Getting Started instructions for your preferred client library. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js/). See [Get Started Using JavaScript](../../javascript/get-started.md) for setup steps.
|
||||
- **Python** with the [`xrpl-py` library](https://xrpl-py.readthedocs.io/). See [Get Started using Python](../../python/get-started.md) for setup steps.
|
||||
- You can also read along and use the interactive steps in your browser without any setup.
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script type="application/javascript" src="/js/tutorials/require-destination-tags.js"></script>
|
||||
|
||||
## Example Code
|
||||
|
||||
Complete sample code for all the steps of these tutorials is available under the [MIT license](https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE).
|
||||
|
||||
- See [Code Samples: Require Destination Tags](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/require-destination-tags/) in the source repository for this website.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Get Credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address and secret key, and some XRP. For development purposes, you can get these using the following interface:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/generate-step.md" /%}
|
||||
|
||||
When you're building production-ready software, you should use an existing account, and manage your keys using a [secure signing configuration](../../../concepts/transactions/secure-signing.md).
|
||||
|
||||
### 2. Connect to the Network
|
||||
|
||||
You must be connected to the network to submit transactions to it. The following code shows how to connect to a public XRP Ledger Testnet server a supported [client library](../../../references/client-libraries.md):
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/get-started/js/base.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/get-started/py/base-async.py" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
For this tutorial, click the following button to connect:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
### 3. Send AccountSet Transaction
|
||||
|
||||
To enable the `RequireDest` flag, set the [`asfRequireDest` value (`1`)](../../../references/protocol/transactions/types/accountset.md#accountset-flags) in the `SetFlag` field of an [AccountSet transaction][]. To send the transaction, you first _prepare_ it to fill out all the necessary fields, then _sign_ it with your account's secret key, and finally _submit_ it to the network.
|
||||
|
||||
For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/require-destination-tags/js/require-destination-tags.js" from="// Send AccountSet" before="// Confirm Account" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/require-destination-tags/py/require-destination-tags.py" from="# Send AccountSet" before="# Confirm Account" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Send AccountSet" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="send-accountset" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait">Send AccountSet</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 4. Wait for Validation
|
||||
|
||||
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. If the XRP Ledger is busy or poor network connectivity delays a transaction from being relayed throughout the network, a transaction may take longer to be confirmed. (For information on how to set an expiration for transactions, see [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).)
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" /%}
|
||||
|
||||
|
||||
### 5. Confirm Account Settings
|
||||
|
||||
After the transaction is validated, you can check your account's settings to confirm that the Require Destination Tag flag is enabled.
|
||||
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/require-destination-tags/js/require-destination-tags.js" from="// Confirm Account" before="// End main()" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/require-destination-tags/py/require-destination-tags.py" from="# Confirm Account" before="# End main()" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
{% interactive-block label="Confirm Settings" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="confirm-settings" class="btn btn-primary previous-steps-required">Confirm Settings</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
For further confirmation, you can send test transactions (from a different address) to confirm that the setting is working as you expect it to. If you a payment with a destination tag, it should succeed, and if you send one _without_ a destination tag, it should fail with the error code [`tecDST_TAG_NEEDED`](../../../references/protocol/transactions/transaction-results/tec-codes.md).
|
||||
|
||||
{% interactive-block label="Test Payments" steps=$frontmatter.steps %}
|
||||
|
||||
<button class="test-payment btn btn-primary" data-dt="10">Send XRP (with Destination Tag)</button>
|
||||
<button class="test-payment btn btn-primary" data-dt="">Send XRP (without Destination Tag)</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Accounts](../../../concepts/accounts/accounts.md)
|
||||
- [Source and Destination Tags](../../../concepts/transactions/source-and-destination-tags.md)
|
||||
- [Transaction Cost](../../../concepts/transactions/transaction-cost.md)
|
||||
- [Payment Types](../../../concepts/payment-types/index.md)
|
||||
- **References:**
|
||||
- [account_info method][]
|
||||
- [AccountSet transaction][]
|
||||
- [AccountRoot Flags](../../../references/protocol/ledger-data/ledger-entry-types/accountroot.md#accountroot-flags)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,403 @@
|
||||
---
|
||||
html: send-a-multi-signed-transaction.html
|
||||
parent: manage-account-settings.html
|
||||
seo:
|
||||
description: Send a transaction authorized with multiple signatures.
|
||||
labels:
|
||||
- Security
|
||||
---
|
||||
# Send a Multi-Signed Transaction
|
||||
|
||||
The following procedure demonstrates how to create, sign, and submit a multi-signed transaction.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You must have already [set up multi-signing](set-up-multi-signing.md) for your address.
|
||||
|
||||
- Multi-signing must be available. Multi-signing has been enabled by an [**Amendment**](../../../concepts/networks-and-servers/amendments.md) to the XRP Ledger Consensus Protocol since 2016-06-27.
|
||||
|
||||
|
||||
## 1. Create the transaction
|
||||
|
||||
Create a JSON object that represents the transaction you want to submit. You have to specify _everything_ about this transaction, including `Fee` and `Sequence`. Also include the field `SigningPubKey` as an empty string, to indicate that the transaction is multi-signed.
|
||||
|
||||
Keep in mind that the `Fee` for multi-signed transactions is significantly higher than for regularly-signed transactions. It should be at least (N+1) times the normal [transaction cost](../../../concepts/transactions/transaction-cost.md), where N is the number of signatures you plan to provide. Since it sometimes takes a while to collect signatures from multiple sources, you may want to specify more than the current minimum, in case the [transaction cost](../../../concepts/transactions/transaction-cost.md) increases in that time.
|
||||
|
||||
Here's an example transaction ready to be multi-signed:
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "TrustSet",
|
||||
"Account": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
"Flags": 262144,
|
||||
"LimitAmount": {
|
||||
"currency": "USD",
|
||||
"issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"value": "100"
|
||||
},
|
||||
"Sequence": 2,
|
||||
"SigningPubKey": "",
|
||||
"Fee": "30000"
|
||||
}
|
||||
```
|
||||
|
||||
(This transaction creates an accounting relationship from `rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC` to `rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh` with a maximum balance of 100 USD.)
|
||||
|
||||
|
||||
## 2. Get one signature
|
||||
|
||||
Use the [sign_for method][] with the secret key and address of one of the members of your SignerList to get a signature for that member.
|
||||
|
||||
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
|
||||
|
||||
|
||||
```
|
||||
$ rippled sign_for rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW <rsA2L..'s secret> '{
|
||||
> "TransactionType": "TrustSet",
|
||||
> "Account": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
> "Flags": 262144,
|
||||
> "LimitAmount": {
|
||||
> "currency": "USD",
|
||||
> "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
> "value": "100"
|
||||
> },
|
||||
> "Sequence": 2,
|
||||
> "SigningPubKey": "",
|
||||
> "Fee": "30000"
|
||||
> }'
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result" : {
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200142200040000240000000263D5038D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E868400000000000753073008114A3780F5CB5A44D366520FC44055E8ED44D9A2270F3E010732102B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF744730450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E58114204288D2E47F8EF6C99BCC457966320D12409711E1F1",
|
||||
"tx_json" : {
|
||||
"Account" : "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
"Fee" : "30000",
|
||||
"Flags" : 262144,
|
||||
"LimitAmount" : {
|
||||
"currency" : "USD",
|
||||
"issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"value" : "100"
|
||||
},
|
||||
"Sequence" : 2,
|
||||
"Signers" : [
|
||||
{
|
||||
"Signer" : {
|
||||
"Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"SigningPubKey" : "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
|
||||
"TxnSignature" : "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
|
||||
}
|
||||
}
|
||||
],
|
||||
"SigningPubKey" : "",
|
||||
"TransactionType" : "TrustSet",
|
||||
"hash" : "A94A6417D1A7AAB059822B894E13D322ED3712F7212CE9257801F96DE6C3F6AE"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Save the `tx_json` field of the response: it has the new signature in the `Signers` field. You can discard the value of the `tx_blob` field.
|
||||
|
||||
If you have a problem in stand-alone mode or a non-production network, check that [multi-sign is enabled](../../../infrastructure/testing-and-auditing/start-a-new-genesis-ledger-in-stand-alone-mode.md#settings-in-new-genesis-ledgers).
|
||||
|
||||
## 3. Get additional signatures
|
||||
|
||||
You can collect additional signatures in parallel or in serial:
|
||||
|
||||
* In parallel: Use the `sign_for` command with the original JSON for the transaction. Each response has a single signature in the `Signers` array.
|
||||
* In serial: Use the `sign_for` command with the `tx_json` value from the previous `sign_for` response. Each response adds a new signature to the existing `Signers` array.
|
||||
|
||||
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
|
||||
|
||||
|
||||
```
|
||||
$ rippled sign_for rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v <rUpy..'s secret> '{
|
||||
> "Account" : "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
> "Fee" : "30000",
|
||||
> "Flags" : 262144,
|
||||
> "LimitAmount" : {
|
||||
> "currency" : "USD",
|
||||
> "issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
> "value" : "100"
|
||||
> },
|
||||
> "Sequence" : 2,
|
||||
> "Signers" : [
|
||||
> {
|
||||
> "Signer" : {
|
||||
> "Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
> "SigningPubKey" : "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
|
||||
> "TxnSignature" : "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
|
||||
> }
|
||||
> }
|
||||
> ],
|
||||
> "SigningPubKey" : "",
|
||||
> "TransactionType" : "TrustSet",
|
||||
> "hash" : "A94A6417D1A7AAB059822B894E13D322ED3712F7212CE9257801F96DE6C3F6AE"
|
||||
> }'
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result" : {
|
||||
"status" : "success",
|
||||
"tx_blob" : "1200142200040000240000000263D5038D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E868400000000000753073008114A3780F5CB5A44D366520FC44055E8ED44D9A2270F3E010732102B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF744730450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E58114204288D2E47F8EF6C99BCC457966320D12409711E1E0107321028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B744630440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC181147908A7F0EDD48EA896C3580A399F0EE78611C8E3E1F1",
|
||||
"tx_json" : {
|
||||
"Account" : "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
"Fee" : "30000",
|
||||
"Flags" : 262144,
|
||||
"LimitAmount" : {
|
||||
"currency" : "USD",
|
||||
"issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"value" : "100"
|
||||
},
|
||||
"Sequence" : 2,
|
||||
"Signers" : [
|
||||
{
|
||||
"Signer" : {
|
||||
"Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"SigningPubKey" : "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
|
||||
"TxnSignature" : "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Signer" : {
|
||||
"Account" : "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
|
||||
"SigningPubKey" : "028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B",
|
||||
"TxnSignature" : "30440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"SigningPubKey" : "",
|
||||
"TransactionType" : "TrustSet",
|
||||
"hash" : "BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Depending on the SignerList you configured, you may need to repeat this step several times to get signatures from all the necessary parties.
|
||||
|
||||
|
||||
## 4. Combine signatures and submit
|
||||
|
||||
If you collected the signatures in serial, the `tx_json` from the last `sign_for` response has all the signatures assembled, so you can use that as the argument to the [submit_multisigned method][].
|
||||
|
||||
If you collected the signatures in parallel, you must manually construct a `tx_json` object with all the signatures included. Take the `Signers` arrays from all the `sign_for` responses, and combine their contents into a single `Signers` array that has each signature. Add the combined `Signers` array to the original transaction JSON value, and use that as the argument to the [submit_multisigned method][].
|
||||
|
||||
```
|
||||
$ rippled submit_multisigned '{
|
||||
> "Account" : "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
> "Fee" : "30000",
|
||||
> "Flags" : 262144,
|
||||
> "LimitAmount" : {
|
||||
> "currency" : "USD",
|
||||
> "issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
> "value" : "100"
|
||||
> },
|
||||
> "Sequence" : 2,
|
||||
> "Signers" : [
|
||||
> {
|
||||
> "Signer" : {
|
||||
> "Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
> "SigningPubKey" : "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
|
||||
> "TxnSignature" : "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
|
||||
> }
|
||||
> },
|
||||
> {
|
||||
> "Signer" : {
|
||||
> "Account" : "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
|
||||
> "SigningPubKey" : "028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B",
|
||||
> "TxnSignature" : "30440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC1"
|
||||
> }
|
||||
> }
|
||||
> ],
|
||||
> "SigningPubKey" : "",
|
||||
> "TransactionType" : "TrustSet",
|
||||
> "hash" : "BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6"
|
||||
> }'
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result": {
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"status": "success",
|
||||
"tx_blob": "1200142200040000240000000263D5038D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E868400000000000753073008114A3780F5CB5A44D366520FC44055E8ED44D9A2270F3E010732102B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF744730450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E58114204288D2E47F8EF6C99BCC457966320D12409711E1E0107321028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B744630440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC181147908A7F0EDD48EA896C3580A399F0EE78611C8E3E1F1",
|
||||
"tx_json": {
|
||||
"Account": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
"Fee": "30000",
|
||||
"Flags": 262144,
|
||||
"LimitAmount": {
|
||||
"currency": "USD",
|
||||
"issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"value": "100"
|
||||
},
|
||||
"Sequence": 2,
|
||||
"Signers": [{
|
||||
"Signer": {
|
||||
"Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"SigningPubKey": "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
|
||||
"TxnSignature": "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
|
||||
}
|
||||
}, {
|
||||
"Signer": {
|
||||
"Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
|
||||
"SigningPubKey": "028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B",
|
||||
"TxnSignature": "30440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC1"
|
||||
}
|
||||
}],
|
||||
"SigningPubKey": "",
|
||||
"TransactionType": "TrustSet",
|
||||
"hash": "BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Take note of the `hash` value from the response so you can check the results of the transaction later. (In this case, the hash is `BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6`.)
|
||||
|
||||
|
||||
## 5. Close the ledger
|
||||
|
||||
If you are using the live network, you can wait 4-7 seconds for the ledger to close automatically.
|
||||
|
||||
If you're running `rippled` in stand-alone mode, use the [ledger_accept method][] to manually close the ledger:
|
||||
|
||||
```
|
||||
$ rippled ledger_accept
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result" : {
|
||||
"ledger_current_index" : 7,
|
||||
"status" : "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 6. Confirm transaction results
|
||||
|
||||
Use the hash value from the response to the `submit_multisigned` command to look up the transaction using the [tx method][]. In particular, check that the `TransactionResult` is the string `tesSUCCESS`.
|
||||
|
||||
On the live network, you must also confirm that the `validated` field is set to the boolean `true`. If the field is not `true`, you might need to wait longer for the consensus process to finish; or your transaction may be unable to be included in a ledger for some reason.
|
||||
|
||||
In stand-alone mode, the server automatically considers a ledger to be `validated` if it has been manually closed.
|
||||
|
||||
```
|
||||
$ rippled tx BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result": {
|
||||
"Account": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
"Fee": "30000",
|
||||
"Flags": 262144,
|
||||
"LimitAmount": {
|
||||
"currency": "USD",
|
||||
"issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"value": "100"
|
||||
},
|
||||
"Sequence": 2,
|
||||
"Signers": [{
|
||||
"Signer": {
|
||||
"Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"SigningPubKey": "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
|
||||
"TxnSignature": "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
|
||||
}
|
||||
}, {
|
||||
"Signer": {
|
||||
"Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
|
||||
"SigningPubKey": "028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B",
|
||||
"TxnSignature": "30440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC1"
|
||||
}
|
||||
}],
|
||||
"SigningPubKey": "",
|
||||
"TransactionType": "TrustSet",
|
||||
"date": 512172510,
|
||||
"hash": "BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6",
|
||||
"inLedger": 6,
|
||||
"ledger_index": 6,
|
||||
"meta": {
|
||||
"AffectedNodes": [{
|
||||
"ModifiedNode": {
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"LedgerIndex": "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8",
|
||||
"PreviousTxnID": "B7E1D33DB7DEA3BB65BFAB2C80E02125F47FCCF6C957A7FDECD915B3EBE0C1DD",
|
||||
"PreviousTxnLgrSeq": 4
|
||||
}
|
||||
}, {
|
||||
"CreatedNode": {
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LedgerIndex": "93E317B32022977C77810A2C558FBB28E30E744C68E73720622B797F957EC5FA",
|
||||
"NewFields": {
|
||||
"Balance": {
|
||||
"currency": "USD",
|
||||
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
||||
"value": "0"
|
||||
},
|
||||
"Flags": 2162688,
|
||||
"HighLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"value": "0"
|
||||
},
|
||||
"LowLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
"value": "100"
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ModifiedNode": {
|
||||
"FinalFields": {
|
||||
"Account": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
"Balance": "999960000",
|
||||
"Flags": 0,
|
||||
"OwnerCount": 6,
|
||||
"Sequence": 3
|
||||
},
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"LedgerIndex": "A6B1BA6F2D70813100908EA84ABB7783695050312735E2C3665259F388804EA0",
|
||||
"PreviousFields": {
|
||||
"Balance": "999990000",
|
||||
"OwnerCount": 5,
|
||||
"Sequence": 2
|
||||
},
|
||||
"PreviousTxnID": "8FDC18960455C196A8C4DE0D24799209A21F4A17E32102B5162BD79466B90222",
|
||||
"PreviousTxnLgrSeq": 5
|
||||
}
|
||||
}, {
|
||||
"ModifiedNode": {
|
||||
"FinalFields": {
|
||||
"Flags": 0,
|
||||
"Owner": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
"RootIndex": "C2728175908D82FB1DE6676F203D8D3C056995A9FA9B369EF326523F1C65A1DE"
|
||||
},
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"LedgerIndex": "C2728175908D82FB1DE6676F203D8D3C056995A9FA9B369EF326523F1C65A1DE"
|
||||
}
|
||||
}, {
|
||||
"CreatedNode": {
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"LedgerIndex": "D8120FC732737A2CF2E9968FDF3797A43B457F2A81AA06D2653171A1EA635204",
|
||||
"NewFields": {
|
||||
"Owner": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"RootIndex": "D8120FC732737A2CF2E9968FDF3797A43B457F2A81AA06D2653171A1EA635204"
|
||||
}
|
||||
}
|
||||
}],
|
||||
"TransactionIndex": 0,
|
||||
"TransactionResult": "tesSUCCESS"
|
||||
},
|
||||
"status": "success",
|
||||
"validated": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,233 @@
|
||||
---
|
||||
html: set-up-multi-signing.html
|
||||
parent: manage-account-settings.html
|
||||
seo:
|
||||
description: Add a signer list to your account to enable multi-signing.
|
||||
labels:
|
||||
- Security
|
||||
---
|
||||
# Set Up Multi-Signing
|
||||
|
||||
[Multi-signing](../../../concepts/accounts/multi-signing.md) is one of three ways to authorize [transactions](../../../concepts/transactions/index.md) for the XRP Ledger, alongside signing with [regular keys and master keys](../../../concepts/accounts/cryptographic-keys.md). You can configure your [address](../../../concepts/accounts/accounts.md) to allow any combination of the three methods to authorize transactions.
|
||||
|
||||
This tutorial demonstrates how to enable multi-signing for an address.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You must have a funded XRP Ledger [address](../../../concepts/accounts/accounts.md) with enough spare XRP to send transactions and meet the [reserve requirement](../../../concepts/accounts/reserves.md) of a new signer list.
|
||||
|
||||
- With the [MultiSignReserve amendment][] enabled, multi-signing requires 2 XRP for the account reserve, regardless of the number of signers and signatures you use. (The MultiSignReserve amendment has been enabled in the production XRP Ledger since **2019-04-07**.)
|
||||
|
||||
- If you are on a test network that does not have the [MultiSignReserve amendment][] enabled, multi-signing requires more than the usual amount of XRP for the [account reserve](../../../concepts/accounts/reserves.md), increasing with the number of signers in the list.
|
||||
|
||||
- You must have access to a tool that can generate key pairs in the XRP Ledger format. If you are using a `rippled` server for this, you must have admin access because the [wallet_propose method][] is admin-only.
|
||||
|
||||
- Alternatively, if you are authorizing others who already have XRP Ledger addresses to be signers for your address, you only need to know the account addresses of those people or entities.
|
||||
|
||||
- Multi-signing must be available. (The MultiSign amendment has been enabled in the production XRP Ledger since **2016-06-27**.)
|
||||
|
||||
## 1. Design Your Configuration
|
||||
|
||||
Decide how many signers you want to include (up to 8). Choose a quorum number for your signer list and weights for your signers based on how many signatures you want to require for a given transaction. For a straightforward "M-of-N" signing setup, assign each signer weight **`1`** and set your list's quorum to be "M", the number of signatures to require.
|
||||
|
||||
|
||||
## 2. Prepare member keys
|
||||
|
||||
You need one or more validly-formed XRP Ledger addresses to include as members of your signer list. You or your chosen signers must know the secret keys associated with these addresses. The addresses can be funded accounts that exist in the ledger, but they do not need to be.
|
||||
|
||||
You can generate new addresses using the [wallet_propose method][]. For example:
|
||||
|
||||
```
|
||||
$ rippled wallet_propose
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result" : {
|
||||
"account_id" : "rnRJ4dpSBKDR2M1itf4Ah6tZZm5xuNZFPH",
|
||||
"key_type" : "secp256k1",
|
||||
"master_key" : "FLOG SEND GOES CUFF GAGE FAT ANTI DEL GUM TIRE ISLE BEAR",
|
||||
"master_seed" : "snheH5UUjU4CWqiNVLny2k21TyKPC",
|
||||
"master_seed_hex" : "A9F859765EB8614D26809836382AFB82",
|
||||
"public_key" : "aBR4hxFXcDNHnGYvTiqb2KU8TTTV1cYV9wXTAuz2DjBm7S8TYEBU",
|
||||
"public_key_hex" : "03C09A5D112B393D531E4F092E3A5769A5752129F0A9C55C61B3A226BB9B567B9B",
|
||||
"status" : "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Take note of the `account_id` (XRP Ledger Address) and `master_seed` (secret key) for each one you generate.
|
||||
|
||||
|
||||
## 3. Send SignerListSet transaction
|
||||
|
||||
[Sign and submit](../../../concepts/transactions/index.md#signing-and-submitting-transactions) a [SignerListSet transaction][] in the normal (single-signature) way. This associates a signer list with your XRP Ledger address, so that a combination of signatures from the members of that signer list can multi-sign later transactions on your behalf.
|
||||
|
||||
In this example, the signer list has 3 members, with the weights and quorum set up such that multi-signed transactions need a signature from `rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW` plus at least one signature from the other two members of the list.
|
||||
|
||||
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
|
||||
|
||||
|
||||
```
|
||||
$ rippled submit shqZZy2Rzs9ZqWTCQAdqc3bKgxnYq '{
|
||||
> "Flags": 0,
|
||||
> "TransactionType": "SignerListSet",
|
||||
> "Account": "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H",
|
||||
> "Fee": "10000",
|
||||
> "SignerQuorum": 3,
|
||||
> "SignerEntries": [
|
||||
> {
|
||||
> "SignerEntry": {
|
||||
> "Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
> "SignerWeight": 2
|
||||
> }
|
||||
> },
|
||||
> {
|
||||
> "SignerEntry": {
|
||||
> "Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
|
||||
> "SignerWeight": 1
|
||||
> }
|
||||
> },
|
||||
> {
|
||||
> "SignerEntry": {
|
||||
> "Account": "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
|
||||
> "SignerWeight": 1
|
||||
> }
|
||||
> }
|
||||
> ]
|
||||
> }'
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result" : {
|
||||
"engine_result" : "tesSUCCESS",
|
||||
"engine_result_code" : 0,
|
||||
"engine_result_message" : "The transaction was applied. Only final in a validated ledger.",
|
||||
"status" : "success",
|
||||
"tx_blob" : "12000C2200000000240000000120230000000368400000000000271073210303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D74473045022100BEDFA12502C66DDCB64521972E5356F4DB965F553853D53D4C69B4897F11B4780220595202D1E080345B65BAF8EBD6CA161C227F1B62C7E72EA5CA282B9434A6F04281142DECAB42CA805119A9BA2FF305C9AFA12F0B86A1F4EB1300028114204288D2E47F8EF6C99BCC457966320D12409711E1EB13000181147908A7F0EDD48EA896C3580A399F0EE78611C8E3E1EB13000181143A4C02EA95AD6AC3BED92FA036E0BBFB712C030CE1F1",
|
||||
"tx_json" : {
|
||||
"Account" : "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H",
|
||||
"Fee" : "10000",
|
||||
"Flags" : 0,
|
||||
"Sequence" : 1,
|
||||
"SignerEntries" : [
|
||||
{
|
||||
"SignerEntry" : {
|
||||
"Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"SignerWeight" : 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry" : {
|
||||
"Account" : "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
|
||||
"SignerWeight" : 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry" : {
|
||||
"Account" : "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
|
||||
"SignerWeight" : 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"SignerQuorum" : 3,
|
||||
"SigningPubKey" : "0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D",
|
||||
"TransactionType" : "SignerListSet",
|
||||
"TxnSignature" : "3045022100BEDFA12502C66DDCB64521972E5356F4DB965F553853D53D4C69B4897F11B4780220595202D1E080345B65BAF8EBD6CA161C227F1B62C7E72EA5CA282B9434A6F042",
|
||||
"hash" : "3950D98AD20DA52EBB1F3937EF32F382D74092A4C8DF9A0B1A06ED25200B5756"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Make sure that the [Transaction Result](../../../references/protocol/transactions/transaction-results/transaction-results.md) is [**`tesSUCCESS`**](../../../references/protocol/transactions/transaction-results/tes-success.md). Otherwise, the transaction failed. If you have a problem in stand-alone mode or a non-production network, check that [multi-sign is enabled](../../../infrastructure/testing-and-auditing/start-a-new-genesis-ledger-in-stand-alone-mode.md#settings-in-new-genesis-ledgers).
|
||||
|
||||
**Note:** Without the [MultiSignReserve amendment][], the more members in the signer list, the more XRP your address must have for purposes of the [owner reserve](../../../concepts/accounts/reserves.md#owner-reserves). If your address does not have enough XRP, the transaction fails with [`tecINSUFFICIENT_RESERVE`](../../../references/protocol/transactions/transaction-results/tec-codes.md). With the [MultiSignReserve amendment][] enabled, the XRP your address must have for purposes of the [owner reserve](../../../concepts/accounts/reserves.md#owner-reserves) is 5 XRP, regardless of the number of members in the signer list. See also: [Signer Lists and Reserves](../../../references/protocol/ledger-data/ledger-entry-types/signerlist.md#signer-lists-and-reserves).
|
||||
|
||||
|
||||
## 4. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
|
||||
## 5. Confirm the new signer list
|
||||
|
||||
Use the [account_objects method][] to confirm that the signer list is associated with the address in the latest validated ledger.
|
||||
|
||||
Normally, an account can own many objects of different types (such as trust lines and offers). If you funded a new address for this tutorial, the signer list is the only object in the response.
|
||||
|
||||
```
|
||||
$ rippled account_objects rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC validated
|
||||
Loading: "/etc/opt/ripple/rippled.cfg"
|
||||
Connecting to 127.0.0.1:5005
|
||||
{
|
||||
"result" : {
|
||||
"account" : "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
|
||||
"account_objects" : [
|
||||
{
|
||||
"Flags" : 0,
|
||||
"LedgerEntryType" : "SignerList",
|
||||
"OwnerNode" : "0000000000000000",
|
||||
"PreviousTxnID" : "8FDC18960455C196A8C4DE0D24799209A21F4A17E32102B5162BD79466B90222",
|
||||
"PreviousTxnLgrSeq" : 5,
|
||||
"SignerEntries" : [
|
||||
{
|
||||
"SignerEntry" : {
|
||||
"Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"SignerWeight" : 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry" : {
|
||||
"Account" : "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
|
||||
"SignerWeight" : 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry" : {
|
||||
"Account" : "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
|
||||
"SignerWeight" : 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"SignerListID" : 0,
|
||||
"SignerQuorum" : 3,
|
||||
"index" : "79FD203E4DDDF2EA78B798C963487120C048C78652A28682425E47C96D016F92"
|
||||
}
|
||||
],
|
||||
"ledger_hash" : "56E81069F06492FB410A70218C08169BE3AB3CFD5AEA20E999662D81DC361D9F",
|
||||
"ledger_index" : 5,
|
||||
"status" : "success",
|
||||
"validated" : true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the signer list is present with the expected contents, then your address is ready to multi-sign.
|
||||
|
||||
## 6. Further steps
|
||||
|
||||
At this point, your address is ready to [send a multi-signed transaction](send-a-multi-signed-transaction.md). You may also want to:
|
||||
|
||||
* [Disable the address's master key pair](disable-master-key-pair.md).
|
||||
* [Remove the address's regular key pair](change-or-remove-a-regular-key-pair.md) (if you previously set one) by sending a [SetRegularKey transaction][].
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md)
|
||||
- [Multi-Signing](../../../concepts/accounts/multi-signing.md)
|
||||
- **Tutorials:**
|
||||
- [Install rippled](../../../infrastructure/installation/index.md)
|
||||
- [Assign a Regular Key Pair](assign-a-regular-key-pair.md)
|
||||
- [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md)
|
||||
- [Enable Public Signing](../../../infrastructure/configuration/enable-public-signing.md)
|
||||
- **References:**
|
||||
- [wallet_propose method][]
|
||||
- [account_objects method][]
|
||||
- [sign_for method][]
|
||||
- [submit_multisigned method][]
|
||||
- [SignerListSet transaction][]
|
||||
- [SignerList object](../../../references/protocol/ledger-data/ledger-entry-types/signerlist.md)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
274
docs/tutorials/tasks/manage-account-settings/use-tickets.md
Normal file
274
docs/tutorials/tasks/manage-account-settings/use-tickets.md
Normal file
@@ -0,0 +1,274 @@
|
||||
---
|
||||
html: use-tickets.html
|
||||
parent: manage-account-settings.html
|
||||
seo:
|
||||
description: Use Tickets to send a transaction outside of normal Sequence order.
|
||||
embed_xrpl_js: true
|
||||
filters:
|
||||
- interactive_steps
|
||||
labels:
|
||||
- Accounts
|
||||
steps: ['Generate', 'Connect', 'Check Sequence', 'Prepare & Sign', 'Submit', 'Wait', 'Intermission', 'Check Tickets', 'Prepare Ticketed Tx', 'Submit Ticketed Tx', 'Wait Again']
|
||||
---
|
||||
# Use Tickets
|
||||
|
||||
[Tickets](../../../concepts/accounts/tickets.md) provide a way to send transactions out of the normal order. This tutorial walks through the steps of creating a Ticket, then using it to send another transaction.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script type="application/javascript" src="/js/tutorials/tasks/use-tickets.js"></script>
|
||||
|
||||
This page provides JavaScript examples that use the [xrpl.js](https://js.xrpl.org/) library. See [Get Started Using JavaScript](../../javascript/get-started.md) for setup instructions.
|
||||
|
||||
Since JavaScript works in the web browser, you can read along and use the interactive steps without any setup.
|
||||
|
||||
|
||||
|
||||
## Steps
|
||||
|
||||
This tutorial is divided into a few phases:
|
||||
|
||||
- (Steps 1-2) **Setup:** You need an XRP Ledger address and secret. For production, you can use the same address and secret consistently. For this tutorial, you can generate new test credentials as needed. You also need to be connected to the network.
|
||||
- (Steps 3-6) **Create Tickets:** Send a transaction to set aside some Tickets.
|
||||
- (Optional) **Intermission:** After creating Tickets, you can send various other transactions at any time before, during, and after the following steps.
|
||||
- (Steps 7-10) **Use Ticket:** Use one of your set-aside Tickets to send a transaction. You can repeat these steps while skipping the previous parts as long as you have at least one Ticket remaining to use.
|
||||
|
||||
### 1. Get Credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address and secret key, and some XRP. For development purposes, you can get these on the [Testnet](../../../concepts/networks-and-servers/parallel-networks.md) using the following interface:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/generate-step.md" /%}
|
||||
|
||||
When you're building production-ready software, you should use an existing account, and manage your keys using a [secure signing configuration](../../../concepts/transactions/secure-signing.md).
|
||||
|
||||
|
||||
### 2. Connect to Network
|
||||
|
||||
You must be connected to the network to submit transactions to it. Since Tickets are only available on Devnet so far, you should connect to a Devnet server. For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Connect to" before="// Get credentials" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Note:** The code samples in this tutorial use JavaScript's [`async`/`await` pattern](https://javascript.info/async-await). Since `await` needs to be used from within an `async` function, the remaining code samples are written to continue inside the `main()` function started here. You can also use Promise methods `.then()` and `.catch()` instead of `async`/`await` if you prefer.
|
||||
|
||||
For this tutorial, click the following button to connect:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
|
||||
### 3. Check Sequence Number
|
||||
|
||||
Before you create any Tickets, you should check what [Sequence Number][] your account is at. You want the current Sequence number for the next step, and the Ticket Sequence numbers it sets aside start from this number.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Check Sequence" before="// Prepare and Sign TicketCreate" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Check Sequence" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="check-sequence" class="btn btn-primary previous-steps-required">Check Sequence Number</button>
|
||||
|
||||
{% loading-icon message="Querying..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
|
||||
### 4. Prepare and Sign TicketCreate
|
||||
|
||||
Construct a [TicketCreate transaction][] using the sequence number you determined in the previous step. Use the `TicketCount` field to specify how many Tickets to create. For example, to prepare a transaction that would make 10 Tickets:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Prepare and Sign TicketCreate" before="// Submit TicketCreate" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Record the transaction's hash and `LastLedgerSequence` value so you can [be sure whether or not it got validated](../../../concepts/transactions/reliable-transaction-submission.md) later.
|
||||
|
||||
|
||||
{% interactive-block label="Prepare & Sign" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="prepare-and-sign" class="btn btn-primary previous-steps-required">Prepare & Sign</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
|
||||
### 5. Submit TicketCreate
|
||||
|
||||
Submit the signed transaction blob that you created in the previous step. For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Submit TicketCreate" before="// Wait for Validation" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Submit" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="ticketcreate-submit" class="btn btn-primary previous-steps-required" data-tx-blob-from="#tx_blob" data-wait-step-name="Wait">Submit</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 6. Wait for Validation
|
||||
|
||||
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. If the XRP Ledger is busy or poor network connectivity delays a transaction from being relayed throughout the network, a transaction may take longer to be confirmed. (For information on how to set an expiration for transactions, see [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).)
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Wait for Validation" before="// Check Available" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" /%}
|
||||
|
||||
|
||||
### (Optional) Intermission
|
||||
|
||||
The power of Tickets is that you can carry on with your account's business as usual while you are getting Ticketed transactions ready. When you want to send a transaction using a Ticket, you can do that in parallel with other sending transactions, including ones using different Tickets, and submit a Ticketed transaction at any time. The only constraint is that each Ticket can only be used once.
|
||||
|
||||
**Tip:** You can come back here to send Sequenced transactions between or during any of the following steps, without interfering with the success of your Ticketed transaction.
|
||||
|
||||
{% interactive-block label="Intermission" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="intermission-payment" class="btn btn-primary previous-steps-required">Payment</button>
|
||||
<button id="intermission-escrowcreate" class="btn btn-primary previous-steps-required">EscrowCreate</button>
|
||||
<button id="intermission-accountset" class="btn btn-primary previous-steps-required">AccountSet</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
|
||||
### 7. Check Available Tickets
|
||||
|
||||
When you want to send a Ticketed transaction, you need to know what Ticket Sequence number to use for it. If you've been keeping careful track of your account, you already know which Tickets you have, but if you're not sure, you can use the [account_objects method][] to look up your available tickets. For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Check Available Tickets" before="// Prepare and Sign Ticketed" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
{% interactive-block label="Check Tickets" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="check-tickets" class="btn btn-primary previous-steps-required">Check Tickets</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
**Tip:** You can repeat the steps from here through the end as long as you have Tickets left to be used!
|
||||
|
||||
### 8. Prepare Ticketed Transaction
|
||||
|
||||
Now that you have a Ticket available, you can prepare a transaction that uses it.
|
||||
|
||||
This can be any [type of transaction](../../../references/protocol/transactions/types/index.md) you like. The following example uses a no-op [AccountSet transaction][] since that doesn't require any other setup in the ledger. Set the `Sequence` field to `0` and include a `TicketSequence` field with the Ticket Sequence number of one of your available Tickets.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Prepare and Sign Ticketed" before="// Submit Ticketed Transaction" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% admonition type="success" name="Tip" %}
|
||||
If you don't plan to submit the TicketCreate transaction right away, you should be sure not to set the `LastLedgerSequence` so that the transaction does not expire. The way you do this varies by library:
|
||||
|
||||
- **xrpl.js:** Specify `"LastLedgerSequence": null` when auto-filling the transaction.
|
||||
- **`rippled`:** Omit `LastLedgerSequence` from the prepared instructions. The server does not provide a value by default.
|
||||
{% /admonition %}
|
||||
|
||||
{% interactive-block label="Prepare Ticketed Tx" steps=$frontmatter.steps %}
|
||||
|
||||
<div id="ticket-selector">
|
||||
<h4>Select a Ticket:</h4>
|
||||
<div class="form-area"></div>
|
||||
</div>
|
||||
<button id="prepare-ticketed-tx" class="btn btn-primary previous-steps-required">Prepare Ticketed Transaction</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 9. Submit Ticketed Transaction
|
||||
|
||||
Submit the signed transaction blob that you created in the previous step. For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Submit Ticketed Transaction" before="// Wait for Validation (again)" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Submit Ticketed Tx" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="ticketedtx-submit" class="btn btn-primary previous-steps-required" data-tx-blob-from="#tx_blob_t" data-wait-step-name="Wait Again">Submit</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 10. Wait for Validation
|
||||
|
||||
Ticketed transactions go through the consensus process the same way that Sequenced transactions do.
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" variables={label: "Wait Again"} /%}
|
||||
|
||||
## With Multi-Signing
|
||||
|
||||
One of the main use cases for Tickets is to be able to collect signatures for several [multi-signed transactions](../../../concepts/accounts/multi-signing.md) in parallel. By using a Ticket, you can send a multi-signed transaction as soon as it is fully signed and ready to go, without worrying about which one will be ready first. <!-- STYLE_OVERRIDE: will -->
|
||||
|
||||
In this scenario, [step 8, "Prepare Ticketed Transaction"](#8-prepare-ticketed-transaction) is slightly different. Instead of preparing and signing all at once, you would follow the steps for [sending any multi-signed transaction](send-a-multi-signed-transaction.md): first prepare the transaction, then circulate it among trusted signers to collect their signatures, and finally combine the signatures into the final multi-signed transaction.
|
||||
|
||||
You could do this in parallel for several different potential transactions as long as each one uses a different Ticket.
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Tickets](../../../concepts/accounts/tickets.md)
|
||||
- [Multi-Signing](../../../concepts/accounts/multi-signing.md)
|
||||
- **Tutorials:**
|
||||
- [Set Up Multi-Signing](set-up-multi-signing.md)
|
||||
- [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md)
|
||||
- **References:**
|
||||
- [account_objects method][]
|
||||
- [sign_for method][]
|
||||
- [submit_multisigned method][]
|
||||
- [TicketCreate transaction][]
|
||||
- [Transaction Common Fields](../../../references/protocol/transactions/common-fields.md)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
488
docs/tutorials/tasks/send-xrp.md
Normal file
488
docs/tutorials/tasks/send-xrp.md
Normal file
@@ -0,0 +1,488 @@
|
||||
---
|
||||
html: send-xrp.html
|
||||
parent: tasks.html
|
||||
seo:
|
||||
description: Learn how to send test payments right from your browser.
|
||||
cta_text: Send XRP
|
||||
labels:
|
||||
- XRP
|
||||
- Payments
|
||||
top_nav_grouping: Popular Pages
|
||||
steps: ['Generate', 'Connect', 'Prepare', 'Sign', 'Submit', 'Wait', 'Check']
|
||||
---
|
||||
# Send XRP
|
||||
|
||||
This tutorial explains how to send a direct XRP Payment using `xrpl.js` for JavaScript, `xrpl-py` for Python, `xrpl4j` for Java or `XRPL_PHP` for PHP. First, we step through the process with the [XRP Ledger Testnet](../../concepts/networks-and-servers/parallel-networks.md). Then, we compare that to the additional requirements for doing the equivalent in production.
|
||||
|
||||
**Tip:** Check out the [Code Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples) for a complete version of the code used in this tutorial.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script type="application/javascript" src="/js/tutorials/send-xrp.js"></script>
|
||||
|
||||
To interact with the XRP Ledger, you need to set up a dev environment with the necessary tools. This tutorial provides examples using the following options:
|
||||
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js/). See [Get Started Using JavaScript](../javascript/get-started.md) for setup steps.
|
||||
- **Python** with the [`xrpl-py` library](https://xrpl-py.readthedocs.io/). See [Get Started using Python](../python/get-started.md) for setup steps.
|
||||
- **Java** with the [xrpl4j library](https://github.com/XRPLF/xrpl4j). See [Get Started Using Java](../java/get-started.md) for setup steps.
|
||||
- **PHP** with the [XRPL_PHP library](https://github.com/AlexanderBuzz/xrpl-php). See [Get Started Using PHP](../php/get-started.md) for setup steps.
|
||||
|
||||
|
||||
## Send a Payment on the Test Net
|
||||
|
||||
### 1. Get Credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address and secret key, and some XRP. The address and secret key look like this:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Example credentials" before="// Connect" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" before="# Connect" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" before="// Connect" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Example credentials" before="// Create" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The secret key shown here is for example only. For development purposes, you can get your own credentials, pre-funded with XRP, on the Testnet using the following interface:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/generate-step.md" /%}
|
||||
|
||||
When you're building production-ready software, you should use an existing account, and manage your keys using a [secure signing configuration](../../concepts/transactions/secure-signing.md).
|
||||
|
||||
|
||||
### 2. Connect to a Testnet Server
|
||||
|
||||
First, you must connect to an XRP Ledger server so you can get the current status of your account and the shared ledger. You can use this information to [automatically fill in some required fields of a transaction](../../references/protocol/transactions/common-fields.md#auto-fillable-fields). You also must be connected to the network to submit transactions to it.
|
||||
|
||||
The following code connects to a public Testnet servers:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/get-started/js/base.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Connect" before="# Get credentials" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Connect" before="// Prepare transaction" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Create a client" before="// Transaction definition" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
For this tutorial, click the following button to connect:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
|
||||
### 3. Prepare Transaction
|
||||
|
||||
Typically, we create XRP Ledger transactions as objects in the JSON [transaction format](../../references/protocol/transactions/index.md). The following example shows a minimal Payment specification:
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "Payment",
|
||||
"Account": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
|
||||
"Amount": "2000000",
|
||||
"Destination": "rUCzEr6jrEyMpjhs4wSdQdz4g8Y382NxfM"
|
||||
}
|
||||
```
|
||||
|
||||
The bare minimum set of instructions you must provide for an XRP Payment is:
|
||||
|
||||
- An indicator that this is a payment. (`"TransactionType": "Payment"`)
|
||||
- The sending address. (`"Account"`)
|
||||
- The address that should receive the XRP (`"Destination"`). This can't be the same as the sending address.
|
||||
- The amount of XRP to send (`"Amount"`). Typically, this is specified as an integer in "drops" of XRP, where 1,000,000 drops equals 1 XRP.
|
||||
|
||||
Technically, a transaction must contain some additional fields, and certain optional fields such as `LastLedgerSequence` are strongly recommended. Some other language-specific notes:
|
||||
|
||||
- If you're using `xrpl.js` for JavaScript, you can use the [`Client.autofill()` method](https://js.xrpl.org/classes/Client.html#autofill) to automatically fill in good defaults for the remaining fields of a transaction. In TypeScript, you can also use the transaction models like `xrpl.Payment` to enforce the correct fields.
|
||||
- With `xrpl-py` for Python, you can use the models in `xrpl.models.transactions` to construct transactions as native Python objects.
|
||||
- With xrpl4j for Java, you can use the model objects in the `xrpl4j-model` module to construct transactions as Java objects.
|
||||
- Unlike the other libraries, you must provide the account `sequence` and the `signingPublicKey` of the source
|
||||
account of a `Transaction` at the time of construction, as well as a `fee`.
|
||||
|
||||
Here's an example of preparing the above payment:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Prepare" before="// Sign" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Prepare" before="# Sign" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Prepare" before="// Sign" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Transaction definition" before="// Sign" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Prepare" steps=$frontmatter.steps %}
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Send: </span>
|
||||
</div>
|
||||
<input type="number" class="form-control" value="22" id="xrp-amount"
|
||||
aria-label="Amount of XRP, as a decimal" aria-describedby="xrp-amount-label"
|
||||
min=".000001" max="100000000000" step="any" />
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text" id="xrp-amount-label"> XRP</span>
|
||||
</div>
|
||||
</div>
|
||||
<button id="prepare-button" class="btn btn-primary previous-steps-required">Prepare
|
||||
example transaction</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 4. Sign the Transaction Instructions
|
||||
|
||||
Signing a transaction uses your credentials to authorize the transaction on your behalf. The input to this step is a completed set of transaction instructions (usually JSON), and the output is a binary blob containing the instructions and a signature from the sender.
|
||||
|
||||
- **JavaScript:** Use the [`sign()` method of a `Wallet` instance](https://js.xrpl.org/classes/Wallet.html#sign) to sign the transaction with `xrpl.js`.
|
||||
- **Python:** Use the [`xrpl.transaction.safe_sign_transaction()` method](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.transaction.html#xrpl.transaction.safe_sign_transaction) with a model and `Wallet` object.
|
||||
- **Java:** Use a [`SignatureService`](https://javadoc.io/doc/org.xrpl/xrpl4j-crypto-core/latest/org/xrpl/xrpl4j/crypto/signing/SignatureService.html) instance to sign the transaction. For this tutorial, use the [`SingleKeySignatureService`](https://javadoc.io/doc/org.xrpl/xrpl4j-crypto-bouncycastle/latest/org/xrpl/xrpl4j/crypto/signing/SingleKeySignatureService.html).
|
||||
- **PHP:** Use a [`sign()` method of a `Wallet` instance](https://alexanderbuzz.github.io/xrpl-php-docs/wallet.html#signing-a-transaction) instance to sign the transaction. The input to this step is a completed array of transaction instructions.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Sign" before="// Submit" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Sign" before="# Submit" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Sign" before="// Submit" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Sign" before="// Submit" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The result of the signing operation is a transaction object containing a signature. Typically, XRP Ledger APIs expect a signed transaction to be the hexadecimal representation of the transaction's canonical [binary format](../../references/protocol/binary-format.md), called a "blob".
|
||||
|
||||
- In `xrpl.js`, the signing API also returns the transaction's ID, or identifying hash, which you can use to look up the transaction later. This is a 64-character hexadecimal string that is unique to this transaction.
|
||||
- In `xrpl-py`, you can get the transaction's hash in the response to submitting it in the next step.
|
||||
- In xrpl4j, `SignatureService.sign` returns a `SignedTransaction`, which contains the transaction's hash, which you can use to look up the transaction later.
|
||||
- In `XRPL_PHP`, the signing API also returns the transaction's ID, or identifying hash, which you can use to look up the transaction later. This is a 64-character hexadecimal string that is unique to this transaction.
|
||||
|
||||
{% interactive-block label="Sign" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="sign-button" class="btn btn-primary previous-steps-required">Sign
|
||||
example transaction</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 5. Submit the Signed Blob
|
||||
|
||||
Now that you have a signed transaction, you can submit it to an XRP Ledger server, which relays it through the network. It's also a good idea to take note of the latest validated ledger index before you submit. The earliest ledger version that your transaction could get into as a result of this submission is one higher than the latest validated ledger when you submit it. Of course, if the same transaction was previously submitted, it could already be in a previous ledger. (It can't succeed a second time, but you may not realize it succeeded if you aren't looking in the right ledger versions.)
|
||||
|
||||
- **JavaScript:** Use the [`submitAndWait()` method of the Client](https://js.xrpl.org/classes/Client.html#submitAndWait) to submit a signed transaction to the network and wait for the response, or use [`submitSigned()`](https://js.xrpl.org/classes/Client.html#submitSigned) to submit a transaction and get only the preliminary response.
|
||||
- **Python:** Use the [`xrpl.transaction.submit_and_wait()` method](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.submit_and_wait) to submit a transaction to the network and wait for a response.
|
||||
- **Java:** Use the [`XrplClient.submit(SignedTransaction)` method](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/org/xrpl/xrpl4j/client/XrplClient.html#submit(org.xrpl.xrpl4j.crypto.signing.SignedTransaction)) to submit a transaction to the network. Use the [`XrplClient.ledger()`](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/org/xrpl/xrpl4j/client/XrplClient.html#ledger(org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams)) method to get the latest validated ledger index.
|
||||
- **PHP:** Use the [`submitAndWait()` method of the Client](https://alexanderbuzz.github.io/xrpl-php-docs/client.html) to submit a transaction to the network and wait for the response.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Submit" before="// Wait" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Submit" before="# Wait" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Submit" before="// Wait" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Submit" before="// Wait" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
This method returns the **tentative** result of trying to apply the transaction to the open ledger. This result _can_ change when the transaction is included in a validated ledger: transactions that succeed initially might ultimately fail, and transactions that fail initially might ultimately succeed. Still, the tentative result often matches the final result, so it's OK to get excited if you see `tesSUCCESS` here. 😁
|
||||
|
||||
If you see any other result, you should check the following:
|
||||
|
||||
- Are you using the correct addresses for the sender and destination?
|
||||
- Did you forget any other fields of the transaction, skip any steps, or make any other typos?
|
||||
- Do you have enough Test XRP to send the transaction? The amount of XRP you can send is limited by the [reserve requirement](../../concepts/accounts/reserves.md), which is currently 10 XRP with an additional 2 XRP for each "object" you own in the ledger. (If you generated a new address with the Testnet Faucet, you don't own any objects.)
|
||||
- Are you connected to a server on the test network?
|
||||
|
||||
See the full list of [transaction results](../../references/protocol/transactions/transaction-results/transaction-results.md) for more possibilities.
|
||||
|
||||
{% interactive-block label="Submit" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="submit-button" class="btn btn-primary previous-steps-required" data-tx-blob-from="#signed-tx-blob" data-wait-step-name="Wait">Submit
|
||||
example transaction</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 6. Wait for Validation
|
||||
|
||||
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. If the XRP Ledger is busy or poor network connectivity delays a transaction from being relayed throughout the network, a transaction may take longer to be confirmed. (For more information on expiration of unconfirmed transactions, see [Reliable Transaction Submission](../../concepts/transactions/reliable-transaction-submission.md).)
|
||||
|
||||
- **JavaScript:** If you used the [`.submitAndWait()` method](https://js.xrpl.org/classes/Client.html#submitAndWait), you can wait until the returned Promise resolves. Other, more asynchronous approaches are also possible.
|
||||
|
||||
- **Python:** If you used the [`xrpl.transaction.submit_and_wait()` method](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.submit_and_wait), you can wait for the function to return. Other approaches, including asynchronous ones using the WebSocket client, are also possible.
|
||||
|
||||
- **Java** Poll the [`XrplClient.transaction()` method](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/org/xrpl/xrpl4j/client/XrplClient.html#transaction(org.xrpl.xrpl4j.model.client.transactions.TransactionRequestParams,java.lang.Class)) to see if your transaction has a final result. Periodically check that the latest validated ledger index has not passed the `LastLedgerIndex` of the transaction using the [`XrplClient.ledger()`](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/org/xrpl/xrpl4j/client/XrplClient.html#ledger(org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams)) method.
|
||||
|
||||
- **PHP:** If you used the [`.submitAndWait()` method](https://alexanderbuzz.github.io/xrpl-php-docs/client.html), you can wait until the returned Promise resolves. Other, more asynchronous approaches are also possible.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Wait" before="// Check" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Wait" before="# Check" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Wait" before="// Check" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Wait" before="// Check" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" /%}
|
||||
|
||||
|
||||
### 7. Check Transaction Status
|
||||
|
||||
To know for sure what a transaction did, you must look up the outcome of the transaction when it appears in a validated ledger version.
|
||||
|
||||
- **JavaScript:** Use the response from `submitAndWait()` or call the [tx method][] using [`Client.request()`](https://js.xrpl.org/classes/Client.html#request).
|
||||
|
||||
**Tip:** In **TypeScript** you can pass a [`TxRequest`](https://js.xrpl.org/interfaces/TxRequest.html) to the [`Client.request()`](https://js.xrpl.org/classes/Client.html#request) method.
|
||||
|
||||
- **Python:** Use the response from [`submit_and_wait()`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.submit_and_wait) or call the [`xrpl.transaction.get_transaction_from_hash()` method](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.transaction.html#xrpl.transaction.get_transaction_from_hash). (See the [tx method response format](../../references/http-websocket-apis/public-api-methods/transaction-methods/tx.md#response-format) for a detailed reference of the fields this can contain.)
|
||||
|
||||
- **Java:** Use the [`XrplClient.transaction()`](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/org/xrpl/xrpl4j/client/XrplClient.html#transaction(org.xrpl.xrpl4j.model.client.transactions.TransactionRequestParams,java.lang.Class)) method to check the status of a transaction.
|
||||
|
||||
- **PHP:** Use the response from `submitAndWait()` or call the `tx method` using [`$client->syncRequest()`](https://alexanderbuzz.github.io/xrpl-php-docs/client.html).
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Check" before="// End of" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Check" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Check" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Check" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Caution:** XRP Ledger APIs may return tentative results from ledger versions that have not yet been validated. For example, in [tx method][] response, be sure to look for `"validated": true` to confirm that the data comes from a validated ledger version. Transaction results that are not from a validated ledger version are subject to change. For more information, see [Finality of Results](../../concepts/transactions/finality-of-results/index.md).
|
||||
|
||||
{% interactive-block label="Check" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="get-tx-button" class="btn btn-primary previous-steps-required">Check transaction status</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
## Differences for Production
|
||||
|
||||
To send an XRP payment on the production XRP Ledger, the steps you take are largely the same. However, there are some key differences in the necessary setup:
|
||||
|
||||
- [Getting real XRP isn't free.](#getting-a-real-xrp-account)
|
||||
- [You must connect to a server that's synced with the production XRP Ledger network.](#connecting-to-the-production-xrp-ledger)
|
||||
|
||||
### Getting a Real XRP Account
|
||||
|
||||
This tutorial uses a button to get an address that's already funded with Test Net XRP, which only works because Test Net XRP is not worth anything. For actual XRP, you need to get XRP from someone who already has some. (For example, you might buy it on an exchange.) You can generate an address and secret that'll work on either production or the Testnet as follows:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
const wallet = new xrpl.Wallet()
|
||||
console.log(wallet.address) // Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
|
||||
console.log(wallet.seed) // Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
```py
|
||||
from xrpl.wallet import Wallet
|
||||
my_wallet = Wallet.create()
|
||||
print(my_wallet.address) # Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
|
||||
print(my_wallet.seed) # Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
```java
|
||||
WalletFactory walletFactory = DefaultWalletFactory.getInstance();
|
||||
SeedWalletGenerationResult generationResult = walletFactory.randomWallet(false);
|
||||
Wallet wallet = generationResult.wallet();
|
||||
System.out.println(wallet.classicAddress()); // Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
|
||||
System.out.println(generationResult.seed()); // Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
```php
|
||||
use XRPL_PHP\Wallet\Wallet;
|
||||
|
||||
$wallet = Wallet::generate();
|
||||
|
||||
print_r("Address: " . $wallet->getAddress()); // Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
|
||||
print_r("Seed: " . $wallet->getSeed()); // Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Warning:** You should only use an address and secret that you generated securely, on your local machine. If another computer generated the address and secret and sent it to you over a network, it's possible that someone else on the network may see that information. If they do, they'll have as much control over your XRP as you do. It's also recommended not to use the same address for the Testnet and Mainnet, because transactions that you created for use on one network could also be valid to execute on the other network, depending on the parameters you provided.
|
||||
|
||||
Generating an address and secret doesn't get you XRP directly; you're only choosing a random number. You must also receive XRP at that address to [fund the account](../../concepts/accounts/accounts.md#creating-accounts). A common way to acquire XRP is to buy it from an exchange, then withdraw it to your own address.
|
||||
|
||||
### Connecting to the Production XRP Ledger
|
||||
|
||||
When you instantiate your client's connect to the XRP Ledger, you must specify a server that's synced with the appropriate [network](../../concepts/networks-and-servers/parallel-networks.md). For many cases, you can use [public servers](../public-servers.md), such as in the following example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
const xrpl = require('xrpl')
|
||||
const api = new xrpl.Client('wss://xrplcluster.com')
|
||||
api.connect()
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
```py
|
||||
from xrpl.clients import JsonRpcClient
|
||||
client = JsonRpcClient("https://xrplcluster.com")
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
```java
|
||||
final HttpUrl rippledUrl = HttpUrl.get("https://xrplcluster.com");
|
||||
XrplClient xrplClient = new XrplClient(rippledUrl);
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
```
|
||||
use XRPL_PHP\Client\JsonRpcClient;
|
||||
|
||||
$client = new JsonRpcClient("https://xrplcluster.com");
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
If you [install `rippled`](../../infrastructure/installation/index.md) yourself, it connects to the production network by default. (You can also [configure it to connect to the test net](../../infrastructure/configuration/connect-your-rippled-to-the-xrp-test-net.md) instead.) After the server has synced (typically within about 15 minutes of starting it up), you can connect to it locally, which has [various benefits](../../concepts/networks-and-servers/index.md). The following example shows how to connect to a server running the default configuration:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
const xrpl = require('xrpl')
|
||||
const api = new xrpl.Client('ws://localhost:6006')
|
||||
api.connect()
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
```py
|
||||
from xrpl.clients import JsonRpcClient
|
||||
client = JsonRpcClient("http://localhost:5005")
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
```java
|
||||
final HttpUrl rippledUrl = HttpUrl.get("http://localhost:5005");
|
||||
XrplClient xrplClient = new XrplClient(rippledUrl);
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
```php
|
||||
use XRPL_PHP\Client\JsonRpcClient;
|
||||
|
||||
$client = new JsonRpcClient("http://localhost:5005");
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Tip:** The local connection uses an unencrypted protocol (`ws` or `http`) rather than the TLS-encrypted version (`wss` or `https`). This is secure only because the communications never leave the same machine, and is easier to set up because it does not require a TLS certificate. For connections on an outside network, always use `wss` or `https`.
|
||||
|
||||
## Next Steps
|
||||
|
||||
After completing this tutorial, you may want to try the following:
|
||||
|
||||
- [Issue a token](../tasks/use-tokens/issue-a-fungible-token.md) on the XRP Ledger Testnet.
|
||||
- [Trade in the Decentralized Exchange](../tasks/use-tokens/trade-in-the-decentralized-exchange.md).
|
||||
- Build [Reliable transaction submission](../../concepts/transactions/reliable-transaction-submission.md) for production systems.
|
||||
- Check your [client library](../../references/client-libraries.md)'s API reference for the full range of XRP Ledger functionality.
|
||||
- Customize your [Account Settings](../tasks/manage-account-settings/index.md).
|
||||
- Learn how [Transaction Metadata](../../references/protocol/transactions/metadata.md) describes the outcome of a transaction in detail.
|
||||
- Explore more [Payment Types](../../concepts/payment-types/index.md) such as Escrows and Payment Channels.
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
html: cancel-a-check.html
|
||||
parent: use-checks.html
|
||||
seo:
|
||||
description: Cancel a Check object without sending money.
|
||||
labels:
|
||||
- Checks
|
||||
---
|
||||
# Cancel a Check
|
||||
|
||||
This tutorial shows how to cancel a [Check](../../../../concepts/payment-types/checks.md), which removes the [Check object from the ledger](../../../../references/protocol/ledger-data/ledger-entry-types/check.md) without sending money.
|
||||
|
||||
You may want to cancel an incoming Check if you do not want it. You might cancel an outgoing Check if you made a mistake when sending it or if circumstances have changed. If a Check expires, it's also necessary to cancel it to remove it from the ledger so the sender gets their [owner reserve](../../../../concepts/accounts/reserves.md#owner-reserves) back.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To cancel a Check with this tutorial, you need the following:
|
||||
|
||||
- You need the ID of a Check object currently in the ledger.
|
||||
- For example, this tutorial includes examples that cancel a Check with the ID `49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0`, although you must use a different ID to go through these steps yourself.
|
||||
- The **address** and **secret key** of a funded account to send the CheckCancel transaction. This address must be either the sender or the recipient of the Check, unless the Check is expired.
|
||||
- A [secure way to sign transactions](../../../../concepts/transactions/secure-signing.md).
|
||||
- A [client library](../../../../references/client-libraries.md) or any HTTP or WebSocket library.
|
||||
|
||||
|
||||
## 1. Prepare the CheckCancel transaction
|
||||
|
||||
Figure out the values of the [CheckCancel transaction][] fields. The following fields are the bare minimum; everything else is either optional or can be [auto-filled](../../../../references/protocol/transactions/common-fields.md#auto-fillable-fields) when signing:
|
||||
|
||||
| Field | Value | Description |
|
||||
|:------------------|:-----------------|:--------------------------------------|
|
||||
| `TransactionType` | String | Use the string `CheckCancel` when canceling a Check. |
|
||||
| `Account` | String (Address) | The address of the sender who is canceling the Check. (In other words, your address.) |
|
||||
| `CheckID` | String | The ID of the Check object in the ledger to cancel. You can get this information by looking up the metadata of the CheckCreate transaction using the [tx method][] or by looking for Checks using the [account_objects method][]. |
|
||||
|
||||
### Example CheckCancel Preparation
|
||||
|
||||
The following examples show how to cancel a Check.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JSON-RPC, WebSocket, or Commandline" %}
|
||||
```json
|
||||
{
|
||||
"TransactionType": "CheckCancel",
|
||||
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
|
||||
"CheckID": "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0",
|
||||
"Fee": "12"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/prepareCancel.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 2. Sign the CheckCancel transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-sign-step.md" /%}
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/signCancel.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/sign-cancel-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/sign-cancel-resp.txt" language="" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/sign-cancel-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
## 3. Submit the signed CheckCancel transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-submit-step.md" /%}
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/submitCancel.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/submit-cancel-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/submit-cancel-resp.txt" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/submit-cancel-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 4. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
## 5. Confirm final result
|
||||
|
||||
Use the [tx method][] with the CheckCancel transaction's identifying hash to check its status. Look for a `"TransactionResult": "tesSUCCESS"` field in the transaction's metadata, indicating that the transaction succeeded, and the field `"validated": true` in the result, indicating that this result is final.
|
||||
|
||||
Look for a `DeletedNode` object in the transaction metadata with `"LedgerEntryType": "Check"` to indicate that the transaction removed a [Check ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/check.md). The `LedgerIndex` of this object should match the ID of the Check.
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/getCancelTx.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/tx-cancel-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/get-cancel-tx-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/tx-cancel-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
html: cash-a-check-for-a-flexible-amount.html
|
||||
parent: use-checks.html
|
||||
seo:
|
||||
description: Cash a Check for as much as possible.
|
||||
labels:
|
||||
- Checks
|
||||
---
|
||||
# Cash a Check for a Flexible Amount
|
||||
|
||||
As long as the Check is in the ledger and not expired, the specified recipient can cash it to receive a flexible amount by sending a [CheckCash transaction][] with a `DeliverMin` field. When cashing a Check in this way, the receiver gets as much as is possible to deliver, debiting the Check's sender for the Check's full `SendMax` amount or as much as is available. Cashing fails if it doesn't deliver at least the `DeliverMin` amount to the Check's recipient.
|
||||
|
||||
You might cash a Check for a flexible amount if you want to get as much as possible from the Check.
|
||||
|
||||
The specified recipient can also [cash the check for an exact amount](cash-a-check-for-a-flexible-amount.md).
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
{% partial file="/docs/_snippets/checkcash-prereqs.md" /%}
|
||||
|
||||
## 1. Prepare the CheckCash transaction
|
||||
|
||||
Figure out the values of the [CheckCash transaction][] fields. To cash a check for a flexible amount, the following fields are the bare minimum; everything else is either optional or can be [auto-filled](../../../../references/protocol/transactions/common-fields.md#auto-fillable-fields) when signing:
|
||||
|
||||
| Field | Value | Description |
|
||||
|:------------------|:--------------------------|:-----------------------------|
|
||||
| `TransactionType` | String | The value `CheckCash` indicates this is a CheckCash transaction. |
|
||||
| `Account` | String (Address) | The address of the sender who is cashing the Check. (In other words, your address.) |
|
||||
| `CheckID` | String | The ID of the Check object in the ledger to cash. You can get this information by looking up the metadata of the CheckCreate transaction using the [tx method][] or by looking for Checks using the [account_objects method][]. |
|
||||
| `DeliverMin` | String or Object (Amount) | A minimum amount to receive from the Check. If you cannot receive at least this much, cashing the Check fails, leaving the Check in the ledger so you can try again. For XRP, this must be a string specifying drops of XRP. For tokens, this is an object with `currency`, `issuer`, and `value` fields. The `currency` and `issuer` fields must match the corresponding fields in the Check object, and the `value` must be less than or equal to the amount in the Check object. For more information on specifying currency amounts, see [Specifying Currency Amounts][]. |
|
||||
|
||||
### Example CheckCash Preparation for a flexible amount
|
||||
|
||||
The following examples show how to prepare a transaction to cash a Check for a flexible amount.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JSON-RPC, WebSocket, or Commandline" %}
|
||||
```json
|
||||
{
|
||||
"Account": "rGPnRH1EBpHeTF2QG8DCAgM7z5pb75LAis",
|
||||
"TransactionType": "CheckCash",
|
||||
"DeliverMin": "95000000",
|
||||
"CheckID": "2E0AD0740B79BE0AAE5EDD1D5FC79E3C5C221D23C6A7F771D85569B5B91195C2"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/prepareCashFlex.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 2. Sign the CheckCash transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-sign-step.md" /%}
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/sign-cash-flex-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/sign-cash-flex-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
## 3. Submit the signed CheckCash transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-submit-step.md" /%}
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/submit-cash-flex-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/submit-cash-flex-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 4. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
## 5. Confirm final result
|
||||
|
||||
Use the [tx method][] with the CheckCash transaction's identifying hash to check its status. Look for a `"TransactionResult": "tesSUCCESS"` field in the transaction's metadata, indicating that the transaction succeeded, and the field `"validated": true` in the result, indicating that this result is final.
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/tx-cash-flex-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/tx-cash-flex-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
### Handling Errors
|
||||
|
||||
If cashing the Check failed with a `tec`-class code, look up the code in the [Full Transaction Response List](../../../../references/protocol/transactions/transaction-results/transaction-results.md) and respond accordingly. Some common possibilities for CheckCash transactions:
|
||||
|
||||
| Result Code | Meaning | How to Respond |
|
||||
|-------------|---------|----------------|
|
||||
| `tecEXPIRED` | The Check has expired. | Cancel the Check and ask the sender to create a new Check with a later Expiration time. |
|
||||
| `tecNO_ENTRY` | The Check ID doesn't exist. | Confirm that the `CheckID` from the CheckCash transaction is correct. Confirm that the Check has not already been canceled or successfully cashed. |
|
||||
| `tecNO_LINE` | The recipient doesn't have a trust line for the Check's currency. | If you want to hold this currency from this issuer, create a trust line for the specified currency and issuer with a reasonable limit using a [TrustSet transaction][], then try to cash the check again. |
|
||||
| `tecNO_PERMISSION` | The sender of the CheckCash transaction isn't the `Destination` of the Check. | Double-check the `Destination` of the Check. |
|
||||
| `tecNO_AUTH` | The issuer of the currency from the check is using [Authorized Trust Lines](../../../../concepts/tokens/fungible-tokens/authorized-trust-lines.md) but the recipient's trust line to the issuer is not approved. | Ask the issuer to authorize this trust line, then try again to cash the Check after they do. |
|
||||
| `tecPATH_PARTIAL` | The Check could not deliver enough tokens, either due to trust line limits or because the sender does not have enough balance of the token to send (including the issuer's [transfer fee](../../../../concepts/tokens/transfer-fees.md), if there is one). | If the problem is the trust line limit, send a [TrustSet transaction][] to increase your limit (if desired) or lower your balance by spending some of the currency, then try to cash the Check again. If the problem is the sender's balance, wait for the sender to have more of the Check's currency, or try again to cash the Check for a lesser amount. |
|
||||
| `tecUNFUNDED_PAYMENT` | The Check could not deliver enough XRP. | Wait for the sender to have more XRP, or try again to cash the Check for a lesser amount. |
|
||||
|
||||
## 6. Confirm delivered amount
|
||||
|
||||
If the Check was cashed for a flexible `DeliverMin` amount and succeeded, you can assume that the Check was cashed for at least the `DeliverMin` amount. To get the exact amount delivered, check the transaction metadata. The `delivered_amount` field in the metadata shows the exact amount delivered. (This field is only provided if the Check was cashed for a flexible amount. If the check was successfully cashed for a fixed amount, then the delivered amount is equal to the `Amount` of the CheckCash transaction.)
|
||||
|
||||
- For XRP, the `AccountRoot` object of the Check's sender has its XRP `Balance` field debited. The `AccountRoot` object of the Check's recipient (the one who sent the CheckCash transaction) has its XRP `Balance` credited for at least the `DeliverMin` of the CheckCash transaction minus the [transaction cost](../../../../concepts/transactions/transaction-cost.md) of sending the transaction.
|
||||
|
||||
For example, the following `ModifiedNode` shows that the account `rGPnRH1EBpHeTF2QG8DCAgM7z5pb75LAis`, the Check's recipient and the sender of this CheckCash transaction, had its XRP balance change from `9999999970` drops to `10099999960` drops, meaning the recipient was credited a _net_ of 99.99999 XRP as a result of processing the transaction.
|
||||
|
||||
```
|
||||
{
|
||||
"ModifiedNode": {
|
||||
"FinalFields": {
|
||||
"Account": "rGPnRH1EBpHeTF2QG8DCAgM7z5pb75LAis",
|
||||
"Balance": "10099999960",
|
||||
"Flags": 0,
|
||||
"OwnerCount": 2,
|
||||
"Sequence": 5
|
||||
},
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"LedgerIndex": "7939126A732EBBDEC715FD3CCB056EB31E65228CA17E3B2901E7D30B90FD03D3",
|
||||
"PreviousFields": {
|
||||
"Balance": "9999999970",
|
||||
"Sequence": 4
|
||||
},
|
||||
"PreviousTxnID": "0283465F0D21BE6B1E91ABDE17266C24C1B4915BAAA9A88CC098A98D5ECD3E9E",
|
||||
"PreviousTxnLgrSeq": 8005334
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The net amount of 99.99999 XRP includes deducting the transaction cost that is destroyed to pay for sending this CheckCash transaction. The following part of the transaction instructions shows that the transaction cost (the `Fee` field) was 10 drops of XRP. By adding this to the net balance change, we conclude that the recipient, `rGPnRH1EBpHeTF2QG8DCAgM7z5pb75LAis`, was credited a _gross_ amount of exactly 100 XRP for cashing the Check.
|
||||
|
||||
```
|
||||
"Account" : "rGPnRH1EBpHeTF2QG8DCAgM7z5pb75LAis",
|
||||
"TransactionType" : "CheckCash",
|
||||
"DeliverMin" : "95000000",
|
||||
"Fee" : "10",
|
||||
```
|
||||
|
||||
- For tokens where the sender or recipient of the check is the issuer, the `RippleState` object representing the trust line between those accounts has its `Balance` adjusted in the favor of the Check's recipient.
|
||||
|
||||
- For tokens with a third-party issuer, there are changes to two `RippleState` objects, representing the trust lines connecting the sender to the issuer, and the issuer to the recipient. The `RippleState` object representing the relationship between the Check's sender and the issuer has its `Balance` changed in favor of the issuer, and the `RippleState` object representing the relationship between the issuer and the recipient has its `Balance` changed in favor of the recipient.
|
||||
|
||||
- If the token has a [transfer fee](../../../../concepts/tokens/transfer-fees.md), the Check's sender may be debited more than the recipient is credited. (The difference is the transfer fee, which is returned to the issuer as a decreased net obligation.)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
html: cash-a-check-for-an-exact-amount.html
|
||||
parent: use-checks.html
|
||||
seo:
|
||||
description: Cash a Check in the ledger for any exact amount up to the amount it specifies.
|
||||
labels:
|
||||
- Checks
|
||||
---
|
||||
# Cash a Check for an Exact Amount
|
||||
|
||||
As long as the Check is in the ledger and not expired, the specified recipient can cash it to receive any exact amount up to the amount specified in the Check by sending a [CheckCash transaction][] with an `Amount` field. You would cash a Check this way if you want to receive a specific amount, for example to pay off an invoice or bill exactly.
|
||||
|
||||
The specified recipient can also [cash the check for a flexible amount](cash-a-check-for-a-flexible-amount.md).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
{% partial file="/docs/_snippets/checkcash-prereqs.md" /%}
|
||||
|
||||
## 1. Prepare the CheckCash transaction
|
||||
|
||||
Figure out the values of the [CheckCash transaction][] fields. To cash a check for an exact amount, the following fields are the bare minimum; everything else is either optional or can be [auto-filled](../../../../references/protocol/transactions/common-fields.md#auto-fillable-fields) when signing:
|
||||
|
||||
| Field | Value | Description |
|
||||
|:------------------|:--------------------------|:-----------------------------|
|
||||
| `TransactionType` | String | The value `CheckCash` indicates this is a CheckCash transaction. |
|
||||
| `Account` | String (Address) | The address of the sender who is cashing the Check. (In other words, your address.) |
|
||||
| `CheckID` | String | The ID of the Check object in the ledger to cash. You can get this information by looking up the metadata of the CheckCreate transaction using the [tx method][] or by looking for Checks using the [account_objects method][]. |
|
||||
| `Amount` | String or Object (Amount) | The amount to redeem from the Check. For XRP, this must be a string specifying drops of XRP. For tokens, this is an object with `currency`, `issuer`, and `value` fields. The `currency` and `issuer` fields must match the corresponding fields in the Check object, and the `value` must be less than or equal to the amount in the Check object. (For currencies with transfer fees, you must cash the Check for less than its `SendMax` so the transfer fee can be paid by the `SendMax`.) If you cannot receive this much, cashing the Check fails, leaving the Check in the ledger so you can try again. For more information on specifying currency amounts, see [Specifying Currency Amounts][]. |
|
||||
|
||||
|
||||
### Example CheckCash Preparation for an exact amount
|
||||
|
||||
The following examples show how to prepare a transaction to cash a Check for a fixed amount.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JSON-RPC, WebSocket, or Commandline" %}
|
||||
```json
|
||||
{
|
||||
"Account": "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy",
|
||||
"TransactionType": "CheckCash",
|
||||
"Amount": "100000000",
|
||||
"CheckID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334",
|
||||
"Fee": "12"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/prepareCashExact.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 2. Sign the CheckCash transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-sign-step.md" /%}
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/sign-cash-exact-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/sign-cash-exact-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
## 3. Submit the signed CheckCash transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-submit-step.md" /%}
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/submit-cash-exact-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/submit-cash-exact-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 4. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
## 5. Confirm final result
|
||||
|
||||
Use the [tx method][] with the CheckCash transaction's identifying hash to check its status. Look for a `"TransactionResult": "tesSUCCESS"` field in the transaction's metadata, indicating that the transaction succeeded, and the field `"validated": true` in the result, indicating that this result is final.
|
||||
|
||||
If the check was cashed for an exact `Amount` and succeeded, you can assume that the recipient was credited for exactly that amount (with possible rounding for very large or very small amounts of tokens).
|
||||
|
||||
If cashing the Check failed, the Check remains in the ledger so you can try cashing again later. You may want to [cash the Check for a flexible amount](cash-a-check-for-a-flexible-amount.md) instead.
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/tx-cash-exact-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/tx-cash-exact-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
html: look-up-checks-by-recipient.html
|
||||
parent: use-checks.html
|
||||
seo:
|
||||
description: Get a list of pending checks sent to an account.
|
||||
labels:
|
||||
- Checks
|
||||
---
|
||||
# Look Up Checks by Recipient
|
||||
|
||||
This tutorial shows how to look up [Checks](../../../../concepts/payment-types/checks.md) by their recipient. You may also want to [look up Checks by sender](look-up-checks-by-sender.md).
|
||||
|
||||
## 1. Look up all Checks for the address
|
||||
|
||||
To get a list of all incoming and outgoing Checks for an account, use the `account_objects` command with the recipient account's address and set the `type` field of the request to `checks`.
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/getChecks.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
{% code-snippet file="/_code-samples/checks/json-rpc/account_objects-req.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/get-checks-resp.txt" language="" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
{% code-snippet file="/_code-samples/checks/json-rpc/account_objects-resp.json" language="json" prefix="200 OK\n\n" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
## 2. Filter the responses by recipient
|
||||
|
||||
The response may include Checks where the account from the request is the sender and Checks where the account is the recipient. Each member of the `account_objects` array of the response represents one Check. For each such Check object, the address in the `Destination` is address of that Check's recipient.
|
||||
|
||||
The following pseudocode demonstrates how to filter the responses by recipient:
|
||||
|
||||
```js
|
||||
recipient_address = "rBXsgNkPcDN2runsvWmwxk3Lh97zdgo9za"
|
||||
account_objects_response = get_account_objects({
|
||||
account: recipient_address,
|
||||
ledger_index: "validated",
|
||||
type: "check"
|
||||
})
|
||||
|
||||
for (i=0; i < account_objects_response.account_objects.length; i++) {
|
||||
check_object = account_objects_response.account_objects[i]
|
||||
if (check_object.Destination == recipient_address) {
|
||||
log("Check to recipient:", check_object)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
html: look-up-checks-by-sender.html
|
||||
parent: use-checks.html
|
||||
seo:
|
||||
description: Get a list of pending Checks sent by an account.
|
||||
labels:
|
||||
- Checks
|
||||
---
|
||||
# Look Up Checks by Sender
|
||||
|
||||
This tutorial shows how to look up [Checks](../../../../concepts/payment-types/checks.md) by their sender. You may also want to [look up Checks by recipient](look-up-checks-by-recipient.md).
|
||||
|
||||
## 1. Look up all Checks for the address
|
||||
|
||||
To get a list of all incoming and outgoing Checks for an account, use the `account_objects` command with the sending account's address and set the `type` field of the request to `checks`.
|
||||
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/getChecks.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
{% code-snippet file="/_code-samples/checks/json-rpc/account_objects-req.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/get-checks-resp.txt" language="" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
{% code-snippet file="/_code-samples/checks/json-rpc/account_objects-resp.json" language="json" prefix="200 OK\n\n" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 2. Filter the responses by sender
|
||||
|
||||
The response may include Checks where the account from the request is the sender and Checks where the account is the recipient. Each member of the `account_objects` array of the response represents one Check. For each such Check object, the address in the `Account` is address of that Check's sender.
|
||||
|
||||
The following pseudocode demonstrates how to filter the responses by sender:
|
||||
|
||||
```js
|
||||
sender_address = "rBXsgNkPcDN2runsvWmwxk3Lh97zdgo9za"
|
||||
account_objects_response = get_account_objects({
|
||||
account: sender_address,
|
||||
ledger_index: "validated",
|
||||
type: "check"
|
||||
})
|
||||
|
||||
for (i=0; i < account_objects_response.account_objects.length; i++) {
|
||||
check_object = account_objects_response.account_objects[i]
|
||||
if (check_object.Account == sender_address) {
|
||||
log("Check from sender:", check_object)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,193 @@
|
||||
---
|
||||
html: send-a-check.html
|
||||
parent: use-checks.html
|
||||
seo:
|
||||
description: Put a Check in the ledger so its intended recipient can cash it later.
|
||||
labels:
|
||||
- Checks
|
||||
---
|
||||
# Send a Check
|
||||
|
||||
Sending a Check is like writing permission for an intended recipient to pull a payment from you. The outcome of this process is a [Check object in the ledger](../../../../references/protocol/ledger-data/ledger-entry-types/check.md) which the recipient can cash later.
|
||||
|
||||
In many cases, you want to send a [Payment][] instead of a Check, since that delivers the money directly to the recipient in one step. However, if your intended recipient uses [DepositAuth](../../../../concepts/accounts/depositauth.md), you cannot send them Payments directly, so a Check is a good alternative.
|
||||
|
||||
This tutorial uses the example of a fictitious company, BoxSend SG (whose XRP Ledger address is `rBXsgNkPcDN2runsvWmwxk3Lh97zdgo9za`) paying a fictitious cryptocurrency consulting company named Grand Payments (with XRP Ledger address `rGPnRH1EBpHeTF2QG8DCAgM7z5pb75LAis`) for some consulting work. Grand Payments prefers be paid in XRP, but to simplify their taxes and regulation, only accepts payments they've explicitly approved.
|
||||
|
||||
Outside of the XRP Ledger, Grand Payments sends an invoice to BoxSend SG with the ID `46060241FABCF692D4D934BA2A6C4427CD4279083E38C77CBE642243E43BE291`, and requests a Check for 100 XRP be sent to Grand Payments' XRP Ledger address of `rGPnRH1EBpHeTF2QG8DCAgM7z5pb75LAis`. <!-- SPELLING_IGNORE: boxsend -->
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To send a Check with this tutorial, you need the following:
|
||||
|
||||
- The **address** and **secret key** of a funded account to send the Check from.
|
||||
- You can use the [XRP Ledger Test Net Faucet](/resources/dev-tools/xrp-faucets) to get a funded address and secret with 10,000 Test Net XRP.
|
||||
- The **address** of a funded account to receive the Check.
|
||||
- A [secure way to sign transactions](../../../../concepts/transactions/secure-signing.md).
|
||||
- A [client library](../../../../references/client-libraries.md) or any HTTP or WebSocket library.
|
||||
|
||||
## 1. Prepare the CheckCreate transaction
|
||||
|
||||
Decide how much money the Check is for and who can cash it. Figure out the values of the [CheckCreate transaction][] fields. The following fields are the bare minimum; everything else is either optional or can be [auto-filled](../../../../references/protocol/transactions/common-fields.md#auto-fillable-fields) when signing:
|
||||
|
||||
| Field | Value | Description |
|
||||
|:------------------|:--------------------------|:-----------------------------|
|
||||
| `TransactionType` | String | Use the string `CheckCreate` here. |
|
||||
| `Account` | String (Address) | The address of the sender who is creating the Check. (In other words, your address.) |
|
||||
| `Destination` | String (Address) | The address of the intended recipient who can cash the Check. |
|
||||
| `SendMax` | String or Object (Amount) | The maximum amount the sender can be debited when this Check gets cashed. For XRP, use a string representing drops of XRP. For tokens, use an object with `currency`, `issuer`, and `value` fields. See [Specifying Currency Amounts][] for details. If you want the recipient to be able to cash the Check for an exact amount of a non-XRP currency with a [transfer fee](../../../../concepts/tokens/transfer-fees.md), remember to include an extra percentage to pay for the transfer fee. (For example, for the recipient to cash a Check for 100 CAD from an issuer with a 2% transfer fee, you must set the `SendMax` to 102 CAD from that issuer.) |
|
||||
|
||||
### Example CheckCreate Preparation
|
||||
|
||||
The following example shows a prepared Check from BoxSend SG (`rBXsgNkPcDN2runsvWmwxk3Lh97zdgo9za`) to Grand Payments (`rGPnRH1EBpHeTF2QG8DCAgM7z5pb75LAis`) for 100 XRP. As additional (optional) metadata, BoxSend SG adds the ID of the invoice from Grand Payments so Grand Payments knows which invoice this Check is intended to pay.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/prepareCreate.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC, WebSocket, or Commandline" %}
|
||||
```json
|
||||
{
|
||||
"TransactionType": "CheckCreate",
|
||||
"Account": "rBXsgNkPcDN2runsvWmwxk3Lh97zdgo9za",
|
||||
"Destination": "rGPnRH1EBpHeTF2QG8DCAgM7z5pb75LAis",
|
||||
"SendMax": "100000000",
|
||||
"InvoiceID": "46060241FABCF692D4D934BA2A6C4427CD4279083E38C77CBE642243E43BE291"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 2. Sign the CheckCreate transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-sign-step.md" /%}
|
||||
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/signCreate.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
{% code-snippet file="/_code-samples/checks/websocket/sign-create-req.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/sign-create-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
#### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/sign-create-resp.txt" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
{% code-snippet file="/_code-samples/checks/websocket/sign-create-resp.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/sign-create-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 3. Submit the signed transaction
|
||||
|
||||
{% partial file="/docs/_snippets/tutorial-submit-step.md" /%}
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/submitCreate.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
{% code-snippet file="/_code-samples/checks/websocket/submit-create-req.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/submit-create-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/submit-create-resp.txt" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
{% code-snippet file="/_code-samples/checks/websocket/submit-create-resp.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/submit-create-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
## 4. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
|
||||
## 5. Confirm final result
|
||||
|
||||
Use the [tx method][] with the CheckCreate transaction's identifying hash to check its status. Look for a `"TransactionResult": "tesSUCCESS"` field in the transaction's metadata, indicating that the transaction succeeded, and the field `"validated": true` in the result, indicating that this result is final.
|
||||
|
||||
Look for a `CreatedNode` object in the transaction metadata with a `LedgerEntryType` of `"Check"`. This indicates that the transaction created a [Check ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/check.md). The `LedgerIndex` of this object is the ID of the Check. In the following example, the Check's ID is `84C61BE9B39B2C4A2267F67504404F1EC76678806C1B901EA781D1E3B4CE0CD9`.
|
||||
|
||||
### Example Request
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/getCreateTx.js" language="" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
{% code-snippet file="/_code-samples/checks/websocket/tx-create-req.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/tx-create-req.sh" language="bash" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
### Example Response
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="ripple-lib 1.x" %}
|
||||
{% code-snippet file="/_code-samples/checks/js/get-create-tx-resp.txt" language="" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
{% code-snippet file="/_code-samples/checks/websocket/tx-create-resp.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
{% code-snippet file="/_code-samples/checks/cli/tx-create-resp.txt" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
html: use-checks.html
|
||||
parent: use-specialized-payment-types.html
|
||||
seo:
|
||||
description: Checks in the XRP Ledger authorize another account to claim funds later, similar to how personal paper checks work.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Checks
|
||||
---
|
||||
# Use Checks
|
||||
|
||||
Checks in the XRP Ledger authorize another account to claim funds later, similar to how personal paper checks work.
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
|
||||
|
||||
{% child-pages /%}
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
html: cancel-an-expired-escrow.html
|
||||
parent: use-escrows.html
|
||||
seo:
|
||||
description: Cancel an expired escrow.
|
||||
labels:
|
||||
- Escrow
|
||||
- Smart Contracts
|
||||
---
|
||||
# Cancel an Expired Escrow
|
||||
|
||||
An escrow in the XRP Ledger is expired when its `CancelAfter` time is lower than the `close_time` of the latest validated ledger. Escrows without a `CancelAfter` time never expire.
|
||||
|
||||
## 1. Get the latest validated ledger
|
||||
|
||||
Use the [ledger method][] to look up the latest validated ledger and get the `close_time` value.
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/ledger-request-expiration.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/ledger-response-expiration.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 2. Look up the escrow
|
||||
|
||||
Use the [account_objects method][] and compare `CancelAfter` to `close_time`:
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/account_objects-request-expiration.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/account_objects-response-expiration.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 3. Submit EscrowCancel transaction
|
||||
|
||||
***Anyone*** can cancel an expired escrow in the XRP Ledger by sending an [EscrowCancel transaction][]. Set the `Owner` field of the transaction to the `Account` of the `EscrowCreate` transaction that created this escrow. Set the `OfferSequence` field to the `Sequence` of the `EscrowCreate` transaction.
|
||||
|
||||
**Tip:** If you don't know what `OfferSequence` to use, you can look up the transaction that created the Escrow: call the [tx method][] with the value of the Escrow's `PreviousTxnID` field. In `tx` response, use the `Sequence` value of that transaction as the `OfferSequence` value of the EscrowCancel transaction.
|
||||
|
||||
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/submit-request-escrowcancel.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/submit-response-escrowcancel.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Take note of the transaction's identifying `hash` value so you can check its final status when it is included in a validated ledger version.
|
||||
|
||||
## 4. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
## 5. Confirm final result
|
||||
|
||||
Use the [tx method][] with the `EscrowCancel` transaction's identifying hash to check its final status. Look in the transaction metadata for a `DeletedNode` with `LedgerEntryType` of `Escrow`. Also look for a `ModifiedNode` of type `AccountRoot` for the sender of the escrowed payment. The `FinalFields` of the object should show the increase in XRP in the `Balance` field for the returned XRP.
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/tx-request-escrowcancel.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/tx-response-escrowcancel.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
In the above example, `r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT` is the sender of the escrow, and the increase in `Balance` from 99999**8**9990 drops to 99999**9**9990 drops represents the return of the escrowed 10,000 drops of XRP (0.01 XRP).
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [What is XRP?](../../../../introduction/what-is-xrp.md)
|
||||
- [Payment Types](../../../../concepts/payment-types/index.md)
|
||||
- [Escrow](../../../../concepts/payment-types/escrow.md)
|
||||
- **Tutorials:**
|
||||
- [Send XRP](../../send-xrp.md)
|
||||
- [Look Up Transaction Results](../../../../concepts/transactions/finality-of-results/look-up-transaction-results.md)
|
||||
- [Reliable Transaction Submission](../../../../concepts/transactions/reliable-transaction-submission.md)
|
||||
- **References:**
|
||||
- [EscrowCancel transaction][]
|
||||
- [EscrowCreate transaction][]
|
||||
- [EscrowFinish transaction][]
|
||||
- [account_objects method][]
|
||||
- [tx method][]
|
||||
- [Escrow ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/escrow.md)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
html: look-up-escrows.html
|
||||
parent: use-escrows.html
|
||||
seo:
|
||||
description: Look up pending escrows by sender or destination address.
|
||||
labels:
|
||||
- Escrow
|
||||
- Smart Contracts
|
||||
---
|
||||
# Look up Escrows
|
||||
|
||||
All pending escrows are stored in the ledger as [Escrow objects](../../../../concepts/payment-types/escrow.md). You can look them up by the sender's address or the destination address.
|
||||
|
||||
**Note:** You can only look up pending escrow objects by destination address if those escrows were created after the [fix1523 amendment][] was enabled on 2017-11-14.
|
||||
|
||||
Use the [account_objects method][], where the sender or destination address is the `account` value.
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/account_objects-request.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The response includes all pending escrow objects with `rfztBskAVszuS3s5Kq7zDS74QtHrw893fm`, where the sender address is the `Account` value, or the destination address is the `Destination` value.
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/account_objects-response.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [What is XRP?](../../../../introduction/what-is-xrp.md)
|
||||
- [Payment Types](../../../../concepts/payment-types/index.md)
|
||||
- [Escrow](../../../../concepts/payment-types/escrow.md)
|
||||
- **Tutorials:**
|
||||
- [Send XRP](../../send-xrp.md)
|
||||
- [Look Up Transaction Results](../../../../concepts/transactions/finality-of-results/look-up-transaction-results.md)
|
||||
- [Reliable Transaction Submission](../../../../concepts/transactions/reliable-transaction-submission.md)
|
||||
- **References:**
|
||||
- [EscrowCancel transaction][]
|
||||
- [EscrowCreate transaction][]
|
||||
- [EscrowFinish transaction][]
|
||||
- [account_objects method][]
|
||||
- [tx method][]
|
||||
- [Escrow ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/escrow.md)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
html: send-a-conditionally-held-escrow.html
|
||||
parent: use-escrows.html
|
||||
seo:
|
||||
description: Create an escrow whose release is based on a condition being fulfilled.
|
||||
labels:
|
||||
- Escrow
|
||||
- Smart Contracts
|
||||
---
|
||||
# Send a Conditionally-Held Escrow
|
||||
|
||||
## 1. Generate condition and fulfillment
|
||||
|
||||
XRP Ledger escrows require PREIMAGE-SHA-256 [crypto-conditions][]. To calculate a condition and fulfillment in the proper format, you should use a crypto-conditions library such as [five-bells-condition](https://github.com/interledgerjs/five-bells-condition). To generate the fulfillment:
|
||||
|
||||
- Use a cryptographically secure source of randomness to generate at least 32 random bytes.
|
||||
- Follow Interledger Protocol's [PSK specification](https://github.com/interledger/rfcs/blob/master/deprecated/0016-pre-shared-key/0016-pre-shared-key.md) and use an HMAC-SHA-256 of the ILP packet as the fulfillment. <!-- SPELLING_IGNORE: psk -->
|
||||
|
||||
Example code for a random fulfillment and condition:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
const cc = require('five-bells-condition')
|
||||
const crypto = require('crypto')
|
||||
|
||||
const preimageData = crypto.randomBytes(32)
|
||||
const fulfillment = new cc.PreimageSha256()
|
||||
fulfillment.setPreimage(preimageData)
|
||||
|
||||
const condition = fulfillment.getConditionBinary().toString('hex').toUpperCase()
|
||||
console.log('Condition:', condition)
|
||||
|
||||
// Keep secret until you want to finish the escrow
|
||||
const fulfillment_hex = fulfillment.serializeBinary().toString('hex').toUpperCase()
|
||||
console.log('Fulfillment:', fulfillment_hex)
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
```py
|
||||
from os import urandom
|
||||
from cryptoconditions import PreimageSha256
|
||||
|
||||
secret = urandom(32)
|
||||
|
||||
fulfillment = PreimageSha256(preimage=secret)
|
||||
|
||||
print("Condition", fulfillment.condition_binary.hex().upper())
|
||||
|
||||
# Keep secret until you want to finish the escrow
|
||||
print("Fulfillment", fulfillment.serialize_binary().hex().upper())
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Save the condition and the fulfillment for later. Be sure to keep the fulfillment secret until you want to finish executing the held payment. Anyone who knows the fulfillment can finish the escrow, releasing the held funds to their intended destination.
|
||||
|
||||
|
||||
## 2. Calculate release or cancel time
|
||||
|
||||
A Conditional `Escrow` transaction must contain either a `CancelAfter` or `FinishAfter` field, or both. The `CancelAfter` field lets the XRP revert to the sender if the condition is not fulfilled before the specified time. The `FinishAfter` field specifies a time before which the escrow cannot execute, even if someone sends the correct fulfillment. Whichever field you provide, the time it specifies must be in the future.
|
||||
|
||||
Example for setting a `CancelAfter` time of 24 hours in the future:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
const rippleOffset = 946684800
|
||||
const CancelAfter = Math.floor(Date.now() / 1000) + (24*60*60) - rippleOffset
|
||||
console.log(CancelAfter)
|
||||
// Example: 556927412
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python 2/3" %}
|
||||
```python
|
||||
from time import time
|
||||
ripple_offset = 946684800
|
||||
cancel_after = int(time()) + (24*60*60) - 946684800
|
||||
print(cancel_after)
|
||||
# Example: 556927412
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Warning:** In the XRP Ledger, you must specify time as **[seconds since the Ripple Epoch][]**. If you use a UNIX time in the `CancelAfter` or `FinishAfter` field without converting it, that sets the unlock time to an extra **30 years** in the future!
|
||||
|
||||
## 3. Submit EscrowCreate transaction
|
||||
|
||||
[Sign and submit](../../../../concepts/transactions/index.md#signing-and-submitting-transactions) an [EscrowCreate transaction][]. Set the `Condition` field of the transaction to the time when the held payment should be released. Set the `Destination` to the recipient, which can be the same address as the sender. Include the `CancelAfter` or `FinishAfter` time you calculated in the previous step. Set the `Amount` to the total amount of [XRP, in drops][], to escrow.
|
||||
|
||||
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/submit-request-escrowcreate-condition.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/submit-response-escrowcreate-condition.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 4. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
## 5. Confirm that the escrow was created
|
||||
|
||||
Use the [tx method][] with the transaction's identifying hash to check its final status. In particular, look for a `CreatedNode` in the transaction metadata to indicate that it created an [Escrow ledger object](../../../../concepts/payment-types/escrow.md).
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/tx-request-escrowcreate-condition.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/tx-response-escrowcreate-condition.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 6. Submit EscrowFinish transaction
|
||||
|
||||
[Sign and submit](../../../../concepts/transactions/index.md#signing-and-submitting-transactions) an [EscrowFinish transaction][] to execute the release of the funds after the `FinishAfter` time has passed. Set the `Owner` field of the transaction to the `Account` address from the EscrowCreate transaction, and the `OfferSequence` to the `Sequence` number from the EscrowCreate transaction. Set the `Condition` and `Fulfillment` fields to the condition and fulfillment values, in hexadecimal, that you generated in step 1. Set the `Fee` ([transaction cost](../../../../concepts/transactions/transaction-cost.md)) value based on the size of the fulfillment in bytes: a conditional EscrowFinish requires at least 330 drops of XRP plus 10 drops per 16 bytes in the size of the fulfillment.
|
||||
|
||||
**Note:** If you included a `FinishAfter` field in the EscrowCreate transaction, you cannot execute it before that time has passed, even if you provide the correct fulfillment for the Escrow's condition. The EscrowFinish transaction fails with the [result code](../../../../references/protocol/transactions/transaction-results/transaction-results.md) `tecNO_PERMISSION` if the previously-closed ledger's close time is before the `FinishAfter` time.
|
||||
|
||||
If the escrow has expired, you can only [cancel the escrow](cancel-an-expired-escrow.md) instead.
|
||||
|
||||
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/submit-request-escrowfinish-condition.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/submit-response-escrowfinish-condition.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Take note of the transaction's identifying `hash` value so you can check its final status when it is included in a validated ledger version.
|
||||
|
||||
## 7. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
## 8. Confirm final result
|
||||
|
||||
Use the [tx method][] with the EscrowFinish transaction's identifying hash to check its final status. In particular, look in the transaction metadata for a `ModifiedNode` of type `AccountRoot` for the destination of the escrowed payment. The `FinalFields` of the object should show the increase in XRP in the `Balance` field.
|
||||
|
||||
Request:
|
||||
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/tx-request-escrowfinish-condition.json" language="json" /%}
|
||||
|
||||
Response:
|
||||
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/tx-response-escrowfinish-condition.json" language="json" /%}
|
||||
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- [Crypto-Conditions Specification][]
|
||||
- **Concepts:**
|
||||
- [What is XRP?](../../../../introduction/what-is-xrp.md)
|
||||
- [Payment Types](../../../../concepts/payment-types/index.md)
|
||||
- [Escrow](../../../../concepts/payment-types/escrow.md)
|
||||
- **Tutorials:**
|
||||
- [Send XRP](../../send-xrp.md)
|
||||
- [Look Up Transaction Results](../../../../concepts/transactions/finality-of-results/look-up-transaction-results.md)
|
||||
- [Reliable Transaction Submission](../../../../concepts/transactions/reliable-transaction-submission.md)
|
||||
- **References:**
|
||||
- [EscrowCancel transaction][]
|
||||
- [EscrowCreate transaction][]
|
||||
- [EscrowFinish transaction][]
|
||||
- [account_objects method][]
|
||||
- [tx method][]
|
||||
- [Escrow ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/escrow.md)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,208 @@
|
||||
---
|
||||
html: send-a-time-held-escrow.html
|
||||
parent: use-escrows.html
|
||||
seo:
|
||||
description: Create an escrow whose only condition for release is that a specific time has passed.
|
||||
labels:
|
||||
- Escrow
|
||||
- Smart Contracts
|
||||
---
|
||||
# Send a Time-Held Escrow
|
||||
|
||||
The [EscrowCreate transaction][] type can create an escrow whose only condition for release is that a specific time has passed. To do this, use the `FinishAfter` field and omit the `Condition` field.
|
||||
|
||||
## 1. Calculate release time
|
||||
|
||||
You must specify the time as whole **[seconds since the Ripple Epoch][]**, which is 946684800 seconds after the UNIX epoch. For example, to release funds at midnight UTC on November 13, 2017:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
// JavaScript Date() is natively expressed in milliseconds; convert to seconds
|
||||
const release_date_unix = Math.floor( new Date("2017-11-13T00:00:00Z") / 1000 );
|
||||
const release_date_ripple = release_date_unix - 946684800;
|
||||
console.log(release_date_ripple);
|
||||
// 563846400
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python 3" %}
|
||||
```python
|
||||
import datetime
|
||||
release_date_utc = datetime.datetime(2017,11,13,0,0,0,tzinfo=datetime.timezone.utc)
|
||||
release_date_ripple = int(release_date_utc.timestamp()) - 946684800
|
||||
print(release_date_ripple)
|
||||
# 563846400
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Warning:** If you use a UNIX time in the `FinishAfter` field without converting to the equivalent Ripple time first, that sets the unlock time to an extra **30 years** in the future!
|
||||
|
||||
## 2. Submit EscrowCreate transaction
|
||||
|
||||
[Sign and submit](../../../../concepts/transactions/index.md#signing-and-submitting-transactions) an [EscrowCreate transaction][]. Set the `FinishAfter` field of the transaction to the time when the held payment should be released. Omit the `Condition` field to make time the only condition for releasing the held payment. Set the `Destination` to the recipient, which may be the same address as the sender. Set the `Amount` to the total amount of [XRP, in drops][], to escrow.
|
||||
|
||||
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/submit-request-escrowcreate-time.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/submit-response-escrowcreate-time.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
Take note of the transaction's identifying `hash` value so you can check its final status when it is included in a validated ledger version.
|
||||
|
||||
## 3. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
## 4. Confirm that the escrow was created
|
||||
|
||||
Use the [tx method][] with the transaction's identifying hash to check its final status. Look for a `CreatedNode` in the transaction metadata to indicate that it created an [Escrow ledger object](../../../../concepts/payment-types/escrow.md).
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/tx-request-escrowcreate-time.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/tx-response-escrowcreate-time.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## 5. Wait for the release time
|
||||
|
||||
Held payments with a `FinishAfter` time cannot be finished until a ledger has already closed with a [`close_time` header field](../../../../references/protocol/ledger-data/ledger-header.md) that is later than the Escrow node's `FinishAfter` time.
|
||||
|
||||
You can check the close time of the most recently-validated ledger with the [ledger method][]:
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/ledger-request.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/ledger-response.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
## 6. Submit EscrowFinish transaction
|
||||
|
||||
[Sign and submit](../../../../concepts/transactions/index.md#signing-and-submitting-transactions) an [EscrowFinish transaction][] to execute the release of the funds after the `FinishAfter` time has passed. Set the `Owner` field of the transaction to the `Account` address from the EscrowCreate transaction, and the `OfferSequence` to the `Sequence` number from the EscrowCreate transaction. For an escrow held only by time, omit the `Condition` and `Fulfillment` fields.
|
||||
|
||||
***TODO: First half of this statement is covered by concept info already. It's also reiterated in escrow.md. The second portion about potential recipients should remain.***
|
||||
**Tip:** The EscrowFinish transaction is necessary because the XRP Ledger's state can only be modified by transactions. The sender of this transaction may be the recipient of the escrow, the original sender of the escrow, or any other XRP Ledger address.
|
||||
|
||||
If the escrow has expired, you can only [cancel the escrow](cancel-an-expired-escrow.md) instead.
|
||||
|
||||
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/submit-request-escrowfinish-time.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/submit-response-escrowfinish-time.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Take note of the transaction's identifying `hash` value so you can check its final status when it is included in a validated ledger version.
|
||||
|
||||
## 7. Wait for validation
|
||||
|
||||
{% partial file="/docs/_snippets/wait-for-validation.md" /%}
|
||||
|
||||
## 8. Confirm final result
|
||||
|
||||
Use the [tx method][] with the EscrowFinish transaction's identifying hash to check its final status. In particular, look in the transaction metadata for a `ModifiedNode` of type `AccountRoot` for the destination of the escrowed payment. The `FinalFields` of the object should show the increase in XRP in the `Balance` field.
|
||||
|
||||
Request:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/tx-request-escrowfinish-time.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Websocket" %}
|
||||
{% code-snippet file="/_code-samples/escrow/websocket/tx-response-escrowfinish-time.json" language="json" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [What is XRP?](../../../../introduction/what-is-xrp.md)
|
||||
- [Payment Types](../../../../concepts/payment-types/index.md)
|
||||
- [Escrow](../../../../concepts/payment-types/escrow.md)
|
||||
- **Tutorials:**
|
||||
- [Send XRP](../../send-xrp.md)
|
||||
- [Look Up Transaction Results](../../../../concepts/transactions/finality-of-results/look-up-transaction-results.md)
|
||||
- [Reliable Transaction Submission](../../../../concepts/transactions/reliable-transaction-submission.md)
|
||||
- **References:**
|
||||
- [EscrowCancel transaction][]
|
||||
- [EscrowCreate transaction][]
|
||||
- [EscrowFinish transaction][]
|
||||
- [account_objects method][]
|
||||
- [tx method][]
|
||||
- [Escrow ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/escrow.md)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
html: use-an-escrow-as-a-smart-contract.html
|
||||
parent: use-escrows.html
|
||||
seo:
|
||||
description: Use a cryptographic escrow as a smart contract to ensure a recipient gets paid only if they successfully perform a service.
|
||||
labels:
|
||||
- Escrow
|
||||
- Smart Contracts
|
||||
---
|
||||
# Use an Escrow as a Smart Contract
|
||||
|
||||
A smart contract is a blockchain-based program that encodes the conditions and fulfillment of an agreement between two or more parties and automatically fulfills the terms of the agreement once conditions are met. A smart contract can help you exchange anything of value in a transparent, traceable, tamper-resistant, and irreversible way.
|
||||
|
||||
The benefit of encoding a smart contract into a blockchain is that it enables the contract to be securely carried out without traditional third-parties, like financial or legal institutions. Instead, the contract is supervised by the distributed, decentralized network of computers that run the blockchain.
|
||||
|
||||
You can use XRP Ledger escrows as smart contracts that release XRP after a certain time has passed or after a cryptographic condition has been fulfilled. In this case, we'll use an escrow as a smart contract that releases XRP after a cryptographic condition has been fulfilled.
|
||||
|
||||
Let's use this scenario to help illustrate this use case: A party planner uses smart contracts to manage payments from party hosts to party vendors. Specifically, the party planner wants to use a smart contract to have the party host pay the party band 2000 XRP once they are done with their set.
|
||||
|
||||
In this use case, the party host is the sender of the escrow, the party band is the receiver of the escrow, and the party planner is playing the role of an _oracle_. In the context of smart contracts, an oracle is a neutral third-party agent that can verify real-world events to either fulfill or invalidate a smart contract. This use case uses a human oracle for illustrative purposes, but in real-life, a software application would more likely play the role of the oracle.
|
||||
|
||||
Using an XRP Ledger escrow to provide this smart contract is a great arrangement because the party planner, as the third-party oracle, never "holds" the funds as one might in a traditional escrow arrangement, and can't possibly take the funds for themselves.
|
||||
|
||||
Here’s a roadmap to the high-level tasks that these participants need to complete to use an escrow as a smart contract.
|
||||
|
||||
|
||||
## Meet the prerequisites
|
||||
|
||||
The party host (sender) must have:
|
||||
|
||||
- An XRP Ledger [account](../../../../concepts/accounts/accounts.md#creating-accounts) that holds enough XRP to pay for escrow and any fees incurred.
|
||||
|
||||
- Access to a secure signing environment, which includes having a network connection to a [`rippled` server](../../../../infrastructure/installation/index.md) (any server) that they can submit signed transactions to. <!--#{ once set up secure signing tutorial is available, link to it from here }# -->
|
||||
|
||||
The party band (receiver) must have:
|
||||
|
||||
- An XRP Ledger [account](../../../../concepts/accounts/accounts.md#creating-accounts) that can receive the XRP paid by the escrow.
|
||||
|
||||
- Access to a [`rippled` server](../../../../infrastructure/installation/index.md) that they can use to look up the details of an XRP Ledger transaction hash and submit the fulfillment value to finish the escrow.
|
||||
|
||||
The party planner (oracle) must have:
|
||||
|
||||
- The ability to generate a condition and a fulfillment.
|
||||
|
||||
- To be able to keep a secret (the fulfillment) until the time is right.
|
||||
|
||||
- A way to communicate the fulfillment publicly or at least to the party band when the time is right.
|
||||
|
||||
- The ability to recognize whether the party band has fulfilled their end of the contract (played at the party).
|
||||
|
||||
|
||||
|
||||
|
||||
## Define the terms of the smart contract
|
||||
|
||||
To create the escrow as a smart contract, the participants must first define the terms of the contract. In this scenario, the participants need to agree on the following details.
|
||||
|
||||
- **Should the escrow disallow fulfillment until a specific time?**
|
||||
|
||||
```
|
||||
While this is an option, the participants agree that it is unnecessary for their escrow. For conditionally-held escrows, enabling this option doesn't provide any additional security, since whether the escrow can be finished still depends entirely on whether the party planner (oracle) publishes the fulfillment before the expiration.
|
||||
```
|
||||
|
||||
- **Should the escrow expire?**
|
||||
|
||||
```
|
||||
Absolutely yes. The participants agree that the escrow should expire after 12 noon the day after the party. This gives the party band (receiver) enough time to finish the escrow, after the party planner verifies that they fulfilled their end of the contract and publishes the cryptographic fulfillment. After expiration, the locked XRP returns to the party host's (sender's) account.
|
||||
|
||||
If the participants don't allow the escrow to expire and the party planner doesn't release the condition, the XRP stays locked in the escrow forever.
|
||||
```
|
||||
|
||||
- **How much XRP should the escrow lock up and potentially pay?**
|
||||
|
||||
```
|
||||
The participants agree that the escrow should lock up and potentially pay 2000 XRP, which is the party band's fee.
|
||||
```
|
||||
|
||||
- **From which XRP Ledger account should the escrow lock up XRP for potential payment to the party band?**
|
||||
|
||||
```
|
||||
The participants agree that the escrow should lock up and potentially pay XRP out of the party host's XRP Ledger account.
|
||||
```
|
||||
|
||||
- **Which XRP Ledger account should the escrow potentially pay XRP to?**
|
||||
|
||||
```
|
||||
The participants agree that the escrow should potentially pay XRP to the party band's XRP Ledger account.
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Oracle: Generate a condition and a fulfillment
|
||||
|
||||
Because participants want to create a conditionally-held escrow to provide the smart contract, they need a condition value and a fulfillment value. In this scenario, the participant that creates these values is the neutral party planner (oracle).
|
||||
|
||||
The party planner generates the condition and fulfillment values. The party planner provides the condition value to the party host, who creates the escrow. The party planner also provides the condition to the party band so that they know that this is the right condition.
|
||||
|
||||
The party planner must keep the fulfillment value a secret. Anyone can use the condition and fulfillment values to finish the escrow. Most often, the receiver finishes the escrow because they're the ones who are motivated to get paid.
|
||||
|
||||
[Generate a condition and a fulfillment >](send-a-conditionally-held-escrow.md#1-generate-condition-and-fulfillment)
|
||||
|
||||
|
||||
## Sender: Calculate time values needed for the escrow
|
||||
|
||||
Because the participants want the escrow to be eligible for cancellation after 12 noon the day after the party, the party host (sender) must calculate a `CancelAfter` value to include in the escrow definition.
|
||||
|
||||
[Calculate time values needed for the escrow >](send-a-conditionally-held-escrow.md#2-calculate-release-or-cancel-time)
|
||||
|
||||
|
||||
|
||||
## Sender: Create the escrow
|
||||
|
||||
The party host (sender) creates the escrow that provides the smart contract. The party host must create the escrow because they are the only participant that can authorize the lock up and potential payout of XRP from their XRP Ledger account.
|
||||
|
||||
[Create the escrow >](send-a-conditionally-held-escrow.md#3-submit-escrowcreate-transaction)
|
||||
|
||||
|
||||
|
||||
## Sender and Receiver: Wait for validation and confirm escrow creation
|
||||
|
||||
The party host (sender) waits for validation of the ledger that contains the escrow creation transaction and then confirms that the escrow was created.
|
||||
|
||||
[Wait for validation >](send-a-conditionally-held-escrow.md#4-wait-for-validation)
|
||||
|
||||
The party host then provides the escrow transaction's `hash` value to the party band (receiver). The party band can use the `hash` value to look up the escrow transaction on the XRP Ledger to ensure that it was created according to the smart contract terms they agreed to. As part of this step, the party band should confirm that the condition matches the one the party planner (oracle) provided. If the condition is wrong, the fulfillment the party planner provides won't let the party band finish the escrow and get paid.
|
||||
|
||||
[confirm escrow creation >](send-a-conditionally-held-escrow.md#5-confirm-that-the-escrow-was-created)
|
||||
|
||||
|
||||
|
||||
## Receiver: Finish the escrow
|
||||
|
||||
The party band (receiver) shows up and plays their set.
|
||||
|
||||
The party planner (oracle) is present at the party to ensure that everything is going smoothly. The party planner confirms first-hand that the party band has fulfilled their contract and publishes the fulfillment publicly, or at least to the party band.
|
||||
|
||||
The party band must finish the escrow before 12 noon. If they don't, the escrow expires and the party band doesn't get paid.
|
||||
|
||||
If the party planner does not publish the fulfillment (the party band is a no show) or if the party planner publishes the fulfillment, but no one finishes the escrow; after 12 noon the next day, anyone can [cancel the escrow](cancel-an-expired-escrow.md). Cancelling the escrow returns the held XRP to the party host's account.
|
||||
|
||||
[Finish the escrow >](send-a-conditionally-held-escrow.md#6-submit-escrowfinish-transaction)
|
||||
|
||||
|
||||
|
||||
## Receiver and Sender: Wait for validation and confirm final result
|
||||
|
||||
The party band (receiver) waits for validation of the ledger that contains the escrow finish transaction and then confirms that the escrow was finished.
|
||||
|
||||
At this time, the party band provides the transaction's `hash` value to the party host (sender). They can use the `hash` value to look up the escrow transaction on the XRP Ledger to ensure that it is been finished correctly.
|
||||
|
||||
The party band can check their XRP Ledger account balance to ensure that their balance has increased by 2000 XRP. The party host's balance won't change at this step (unless the escrow was canceled) because the escrow creation already debited the locked-up XRP from their account.
|
||||
|
||||
[Wait for validation >](send-a-conditionally-held-escrow.md#7-wait-for-validation)
|
||||
|
||||
[confirm final result >](send-a-conditionally-held-escrow.md#8-confirm-final-result)
|
||||
@@ -0,0 +1,667 @@
|
||||
---
|
||||
html: use-payment-channels.html
|
||||
parent: use-specialized-payment-types.html
|
||||
seo:
|
||||
description: Payment Channels are an advanced feature for sending "asynchronous" XRP payments that can be divided into very small increments and settled later. This tutorial walks through the entire process of using a payment channel, with examples using the JSON-RPC API of a local rippled server.
|
||||
labels:
|
||||
- Payment Channels
|
||||
- Smart Contracts
|
||||
---
|
||||
# Use Payment Channels
|
||||
|
||||
[Payment Channels](../../../../concepts/payment-types/payment-channels.md) are an advanced feature for sending "asynchronous" XRP payments that can be divided into very small increments and settled later. This tutorial walks through the entire process of using a payment channel, with examples using the [JSON-RPC API](../../../../references/http-websocket-apis/index.md) of a local [`rippled` server](../../../../concepts/networks-and-servers/index.md).
|
||||
|
||||
Ideally, to step through this tutorial, you would have two people, each with the keys to a [funded XRP Ledger account](../../../../concepts/accounts/accounts.md). However, you can also step through the tutorial as one person managing two XRP Ledger addresses.
|
||||
|
||||
## Example Values
|
||||
|
||||
The example addresses used in this tutorial are:
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| **Payer's address** | `rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH` |
|
||||
| **Public key used for channel (in the XRP Ledger's [base58][] encoded string format)** | `aB44YfzW24VDEJQ2UuLPV2PvqcPCSoLnL7y5M1EzhdW4LnK5xMS3`
|
||||
| **Public key used for channel (in hex)** | `023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6` |
|
||||
| **Payee's address** | `rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn` |
|
||||
|
||||
**Tip:** In this example, the channel's public key is the public key from the payer's master key pair. This is perfectly safe and valid. It is also perfectly safe and valid to use a different key pair, as long as only the payer knows the public and secret keys for that key pair. <!-- Editor's note: We don't have a good page to link to explain key pairs as of time of this writing. -->
|
||||
|
||||
Additionally, you'll need a `rippled` server to send transactions to. The examples in this tutorial assume a `rippled` server is running on the test machine (`localhost`) with an unencrypted JSON-RPC API endpoint on port **5005**.
|
||||
|
||||
To test without transferring real XRP, you can use [XRP Ledger Testnet](/resources/dev-tools/xrp-faucets) addresses with Testnet XRP. If you do use the Testnet, you can use the Testnet servers' JSON-RPC API by connecting to `https://api.altnet.rippletest.net:51234` instead of `http://localhost:5005/`.
|
||||
|
||||
You can use any amount of XRP for the payment channels. The example values in this tutorial set aside 100 XRP (`100000000` drops) in a payment channel for at least 1 day.
|
||||
|
||||
## Flow Diagram
|
||||
[flow diagram]: #flow-diagram
|
||||
|
||||
The following diagram summarizes the lifecycle of a payment channel:
|
||||
|
||||
[](/docs/img/paychan-flow.png)
|
||||
|
||||
You can match up the numbered steps in this diagram with the steps of this tutorial.
|
||||
|
||||
1. [Payer: Create channel](#1-the-payer-creates-a-payment-channel-to-a-particular-recipient)
|
||||
2. [Payee: Check channel](#2-the-payee-checks-specifics-of-the-payment-channel)
|
||||
3. [Payer: Sign claims](#3-the-payer-creates-one-or-more-signed-claims-for-the-xrp-in-the-channel)
|
||||
4. [Payer: Send claim(s) to payee](#4-the-payer-sends-a-claim-to-the-payee-as-payment-for-goods-or-services)
|
||||
5. [Payee: Verify claims](#5-the-payee-verifies-the-claims)
|
||||
6. [Payee: Provide goods or services](#6-payee-provides-goods-or-services)
|
||||
7. [Repeat steps 3-6 as desired](#7-repeat-steps-3-6-as-desired)
|
||||
8. [Payee: Redeem claim](#8-when-ready-the-payee-redeems-a-claim-for-the-authorized-amount)
|
||||
9. [Payer: Request to close channel](#9-when-the-payer-and-payee-are-done-doing-business-the-payer-requests-for-the-channel-to-be-closed)
|
||||
10. [Payer (or anyone else): Close expired channel](#10-anyone-can-close-the-expired-channel)
|
||||
|
||||
## 1. The payer creates a payment channel to a particular recipient.
|
||||
|
||||
This is a [PaymentChannelCreate transaction][]. As part of this process, the payer sets certain specifics of the channel like an expiration time and a settlement delay, which affect the guarantees around the claims in the channel. The payer also sets the public key that will be used to verify claims against the channel. <!-- STYLE_OVERRIDE: will -->
|
||||
|
||||
**Tip:** The "settlement delay" does not delay the settlement, which can happen as fast as a ledger version closes (3-5 seconds). The "settlement delay" is a forced delay on closing the channel so that the payee has a chance to finish with settlement.
|
||||
|
||||
The following example shows creation of a payment channel by [submitting](../../../../references/http-websocket-apis/public-api-methods/transaction-methods/submit.md#sign-and-submit-mode) to a local `rippled` server with the JSON-RPC API. The payment channel allocates 100 XRP from the [example payer](#example-values) (`rN7n7...`) to the [example payee](#example-values) (`rf1Bi...`) with a settlement delay of 1 day. The public key is the example payer's master public key, in hexadecimal.
|
||||
|
||||
**Note:** A payment channel counts as one object toward the payer's [owner reserve](../../../../concepts/accounts/reserves.md#owner-reserves). The owner must keep at least enough XRP to satisfy the reserve after subtracting the XRP allocated to the payment channel.
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
POST http://localhost:5005/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"method": "submit",
|
||||
"params": [{
|
||||
"secret": "s████████████████████████████",
|
||||
"tx_json": {
|
||||
"Account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"TransactionType": "PaymentChannelCreate",
|
||||
"Amount": "100000000",
|
||||
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"SettleDelay": 86400,
|
||||
"PublicKey": "023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6",
|
||||
"DestinationTag": 20170428
|
||||
},
|
||||
"fee_mult_max": 1000
|
||||
}]
|
||||
}
|
||||
```json
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
200 OK
|
||||
|
||||
{
|
||||
"result": {
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
...
|
||||
"tx_json": {
|
||||
...
|
||||
"TransactionType": "PaymentChannelCreate",
|
||||
"hash": "3F93C482C0BC2A1387D9E67DF60BECBB76CC2160AE98522C77AF0074D548F67D"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
The immediate response to the `submit` request contains a _provisional_ result with the transaction's identifying `hash` value. The payer should check the transaction's _final_ result in a validated ledger and get the Channel ID from the metadata. This can be done with the `tx` command:
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
POST http://localhost:5005/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"method": "tx",
|
||||
"params": [{
|
||||
"transaction": "3F93C482C0BC2A1387D9E67DF60BECBB76CC2160AE98522C77AF0074D548F67D"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
200 OK
|
||||
|
||||
{
|
||||
"result": {
|
||||
"Account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"Amount": "100000000",
|
||||
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
...
|
||||
"TransactionType": "PaymentChannelCreate",
|
||||
...
|
||||
"hash": "3F93C482C0BC2A1387D9E67DF60BECBB76CC2160AE98522C77AF0074D548F67D",
|
||||
"inLedger": 29380080,
|
||||
"ledger_index": 29380080,
|
||||
"meta": {
|
||||
"AffectedNodes": [
|
||||
...
|
||||
{
|
||||
"CreatedNode": {
|
||||
"LedgerEntryType": "PayChannel",
|
||||
"LedgerIndex": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"NewFields": {
|
||||
"Account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"Amount": "100000000",
|
||||
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"DestinationTag": 20170428,
|
||||
"PublicKey": "023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6",
|
||||
"SettleDelay": 86400
|
||||
}
|
||||
}
|
||||
},
|
||||
...
|
||||
],
|
||||
"TransactionIndex": 16,
|
||||
"TransactionResult": "tesSUCCESS"
|
||||
},
|
||||
"status": "success",
|
||||
"validated": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the response from the JSON-RPC, the payer should look for the following:
|
||||
|
||||
- In the transaction's `meta` field, confirm that the `TransactionResult` is `tesSUCCESS`.
|
||||
- Confirm that the response has `"validated":true` to indicate the data comes from a validated ledger. (The result `tesSUCCESS` is only [final](../../../../concepts/transactions/finality-of-results/index.md) if it appears in a validated ledger version.)
|
||||
- In the `AffectedNodes` array of the transaction's `meta` field, look for a `CreatedNode` object with the `LedgerEntryType` of `PayChannel`. The `LedgerIndex` field of the `CreatedNode` object indicates the Channel ID. (In the above example, this is a hex string starting with "`5DB0`...") The Channel ID is necessary later to sign claims.
|
||||
For more information on the PayChannel ledger object type, see [PayChannel ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/paychannel.md).
|
||||
|
||||
|
||||
## 2. The payee checks specifics of the payment channel.
|
||||
|
||||
You can look up payment channels with the [account_channels method][], using the payer of the channel, as in the following example (using the JSON-RPC API):
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
POST http://localhost:5005/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"method": "account_channels",
|
||||
"params": [{
|
||||
"account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"ledger_index": "validated"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
200 OK
|
||||
|
||||
{
|
||||
"result": {
|
||||
"account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"channels": [{
|
||||
"account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"amount": "100000000",
|
||||
"balance": "0",
|
||||
"channel_id": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"destination_tag": 20170428,
|
||||
"public_key": "aB44YfzW24VDEJQ2UuLPV2PvqcPCSoLnL7y5M1EzhdW4LnK5xMS3",
|
||||
"public_key_hex": "023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6",
|
||||
"settle_delay": 86400
|
||||
}],
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The payee should check that the parameters of the payment channel are suitable for their specific use case, including all of the following:
|
||||
|
||||
- Confirm the `destination_account` field has the payee's correct address.
|
||||
- Confirm the `settle_delay` field has a settlement delay in seconds that provides enough time for the payee to redeem outstanding claims.
|
||||
- Confirm the fields `cancel_after` (immutable expiration) and `expiration` (mutable expiration), if they are present, are not too soon. The payee should take note of these times so they can be sure to redeem claims before then.
|
||||
- Take note of the `public_key` and `channel_id` fields. These are necessary later to verify and redeem claims.
|
||||
- _(Optional)_ Confirm the `destination_tag` field is present and has a desired destination tag.
|
||||
|
||||
Since there can be multiple channels between the same two parties, it is important for the payee to check the qualities of the correct channel. If there is any chance of confusion, the payer should clarify the Channel ID (`channel_id`) of the channel to use.
|
||||
|
||||
|
||||
## 3. The payer creates one or more signed _claims_ for the XRP in the channel.
|
||||
|
||||
The amounts of these claims depends on the specific goods or services the payer wants to pay for.
|
||||
|
||||
Each claim must be for a cumulative amount. In other words, to buy two items at 10 XRP each, the first claim should have an amount of 10 XRP and the second claim should have an amount of 20 XRP. The claim can never be more than the total amount of XRP allocated to the channel. (A [PaymentChannelFund][] transaction can increase the total amount of XRP allocated to the channel.)
|
||||
|
||||
You can create claims with the [channel_authorize method][]. The following example authorizes 1 XRP from the channel:
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
POST http://localhost:5005/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"method": "channel_authorize",
|
||||
"params": [{
|
||||
"channel_id": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"secret": "s████████████████████████████",
|
||||
"amount": "1000000"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"signature": "304402204EF0AFB78AC23ED1C472E74F4299C0C21F1B21D07EFC0A3838A420F76D783A400220154FB11B6F54320666E4C36CA7F686C16A3A0456800BBC43746F34AF50290064",
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 4. The payer sends a claim to the payee as payment for goods or services.
|
||||
|
||||
This communication happens "off-ledger" in any communication system the payer and payee can agree to. You should use secure communications for this, but it's not strictly necessary. Only the payer or payee of a channel can redeem claims against that channel.
|
||||
|
||||
The exact format of the claim is not important as long as it communicates the following information:
|
||||
|
||||
| Field | Example |
|
||||
|:------------------------|:---------------------------------------------------|
|
||||
| Channel ID | `5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3` |
|
||||
| Amount of XRP, in drops | `1000000` |
|
||||
| Signature | `304402204EF0AFB78AC23ED1C472E74F4299C0C21F1B21D07EFC0A3838A420F76D783A` <br/> `400220154FB11B6F54320666E4C36CA7F686C16A3A0456800BBC43746F34AF50290064` _(Note: this long string has been broken to fit on one line.)_ |
|
||||
|
||||
The payee also needs to know the Public Key associated with the channel, which is the same throughout the channel's life.
|
||||
|
||||
## 5. The payee verifies the claims.
|
||||
|
||||
You can verify claims using the [channel_verify method][]. The payee should confirm that the amount of the claim is equal to or greater than the total price of goods and services provided. (Since the amount is cumulative, this is the total price of all goods and services bought so far.)
|
||||
|
||||
Example of using `channel_verify` with the JSON-RPC API:
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
POST http://localhost:5005/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"method": "channel_verify",
|
||||
"params": [{
|
||||
"channel_id": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"signature": "304402204EF0AFB78AC23ED1C472E74F4299C0C21F1B21D07EFC0A3838A420F76D783A400220154FB11B6F54320666E4C36CA7F686C16A3A0456800BBC43746F34AF50290064",
|
||||
"public_key": "aB44YfzW24VDEJQ2UuLPV2PvqcPCSoLnL7y5M1EzhdW4LnK5xMS3",
|
||||
"amount": "1000000"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
200 OK
|
||||
|
||||
{
|
||||
"result": {
|
||||
"signature_verified":true,
|
||||
"status":"success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the response shows `"signature_verified": true` then the claim's signature is genuine. The payee must **also** confirm that the channel has enough XRP available to honor the claim. To do this, the payee uses the [account_channels method][] to confirm the most recent validated state of the payment channel.
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
POST http://localhost:5005/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"method": "account_channels",
|
||||
"params": [{
|
||||
"account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"ledger_index": "validated"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
200 OK
|
||||
|
||||
{
|
||||
"result": {
|
||||
"account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"channels": [{
|
||||
"account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"amount": "100000000",
|
||||
"balance": "0",
|
||||
"channel_id": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"destination_tag": 20170428,
|
||||
"public_key": "aB44YfzW24VDEJQ2UuLPV2PvqcPCSoLnL7y5M1EzhdW4LnK5xMS3",
|
||||
"public_key_hex": "023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6",
|
||||
"settle_delay": 86400
|
||||
}],
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The payee should check the following:
|
||||
|
||||
- Find the object in the `channels` array whose `channel_id` matches the Channel ID of the claim. It is possible to have multiple payment channels, even between the same parties, but a claim can only be redeemed against the channel with the matching ID.
|
||||
- Confirm that the `expiration` (mutable expiration) of the channel, if present, is not too soon. The payee must redeem claims before this time.
|
||||
- Confirm that the `amount` of the claim is equal or less than the `amount` of the channel. If the `amount` of the claim is higher, the claim cannot be redeemed unless the payer uses a [PaymentChannelFund transaction][] to increase the total amount of XRP available to the channel.
|
||||
- Confirm that the `balance` of the channel matches the amount the payee expects to have already received from the channel. If these do not match up, the payee should double-check the channel's transaction history. Some possible explanations for a mismatch include:
|
||||
- The payer used a [PaymentChannelClaim][] transaction to deliver XRP from the channel to the payee, but the payee did not notice and record the incoming transaction.
|
||||
- The payee's records include transactions that are "in flight" or have not yet been included in the latest validated ledger version. The payee can use the [tx method][] to look up the state of individual transactions to check this.
|
||||
- The `account_channels` request did not specify the correct ledger version. (Use `"ledger_index": "validated"` to get the latest validated ledger version)
|
||||
- The payee previously redeemed XRP but forgot to record it.
|
||||
- The payee attempted to redeem XRP and recorded the tentative result, but the transaction's final validated result was not the same and the payee neglected to record the final validated result.
|
||||
- The `rippled` server the payee queried has lost sync with the rest of the network or is experiencing an unknown bug. Use the [server_info method][] to check the state of the server. (If you can reproduce this situation, please [report an issue](https://github.com/XRPLF/rippled/issues/).)
|
||||
|
||||
After confirming both the signature and the current state of the payment channel, the payee has not yet received the XRP, but is certain that he or she _can_ redeem the XRP as long as the transaction to do so is processed before the channel expires.
|
||||
|
||||
|
||||
## 6. Payee provides goods or services.
|
||||
|
||||
At this point, the payee can provide goods and services to the payer, knowing that payment is already assured.
|
||||
|
||||
For purposes of this tutorial, the payee can give the payer a high-five or equivalent online message as the "goods and services".
|
||||
|
||||
|
||||
## 7. Repeat steps 3-6 as desired.
|
||||
|
||||
The payer and payee can repeat steps 3 through 6 (creating, transmitting, and verifying claims in exchange for goods and services) as many times and as often as they like without waiting for the XRP Ledger itself. The two main limits of this process are:
|
||||
|
||||
- The amount of XRP in the payment channel. (If necessary, the payer can send a [PaymentChannelFund transaction][] to increase the total amount of XRP available to the channel.)
|
||||
|
||||
- The immutable expiration of the payment channel, if one is set. (The `cancel_after` field in the response to the [account_channels method][] shows this.)
|
||||
|
||||
|
||||
## 8. When ready, the payee redeems a claim for the authorized amount.
|
||||
|
||||
This is the point where the payee finally receives some XRP from the channel.
|
||||
|
||||
This is a [PaymentChannelClaim transaction][] with the `Balance`, `Amount`, `Signature`, and `PublicKey` fields provided. Because claim values are cumulative, the payee only needs to redeem the largest (most recent) claim to get the full amount. The payee is not required to redeem the claim for the full amount authorized.
|
||||
|
||||
The payee can do this multiple times, to settle partially while still doing business, if desired.
|
||||
|
||||
Example of claiming XRP from a channel:
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
POST http://localhost:5005/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"method": "submit",
|
||||
"params": [{
|
||||
"secret": "s████████████████████████████",
|
||||
"tx_json": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"TransactionType": "PaymentChannelClaim",
|
||||
"Amount": "1000000",
|
||||
"Balance": "1000000",
|
||||
"Channel": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"PublicKey": "023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6",
|
||||
"Signature": "304402204EF0AFB78AC23ED1C472E74F4299C0C21F1B21D07EFC0A3838A420F76D783A400220154FB11B6F54320666E4C36CA7F686C16A3A0456800BBC43746F34AF50290064"
|
||||
},
|
||||
"fee_mult_max": 1000
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
200 OK
|
||||
|
||||
{
|
||||
"result": {
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"status": "success",
|
||||
"tx_blob": "12000F2280000000240000017450165DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB36140000000000F42406240000000000F424068400000000000000A7121023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7447304502210096B933BC24DA77D8C4057B4780B282BA66C668DFE1ACF4EEC063AD6661725797022037C8823669CE91AACA8CC754C9F041359F85B0B32384AEA141EBC3603798A24C7646304402204EF0AFB78AC23ED1C472E74F4299C0C21F1B21D07EFC0A3838A420F76D783A400220154FB11B6F54320666E4C36CA7F686C16A3A0456800BBC43746F34AF5029006481144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
|
||||
"tx_json": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Amount": "1000000",
|
||||
"Balance": "1000000",
|
||||
"Channel": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"PublicKey": "023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6",
|
||||
"Sequence": 372,
|
||||
"Signature": "304402204EF0AFB78AC23ED1C472E74F4299C0C21F1B21D07EFC0A3838A420F76D783A400220154FB11B6F54320666E4C36CA7F686C16A3A0456800BBC43746F34AF50290064",
|
||||
"SigningPubKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
|
||||
"TransactionType": "PaymentChannelClaim",
|
||||
"TxnSignature": "304502210096B933BC24DA77D8C4057B4780B282BA66C668DFE1ACF4EEC063AD6661725797022037C8823669CE91AACA8CC754C9F041359F85B0B32384AEA141EBC3603798A24C",
|
||||
"hash": "C9FE08FC88CF76C3B06622ADAA47AE99CABB3380E4D195E7751274CFD87910EB"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The payee should confirm that this transaction is successful in a validated ledger. For the full details, see [Reliable Transaction Submission](../../../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
## 9. When the payer and payee are done doing business, the payer requests for the channel to be closed.
|
||||
|
||||
This is a [PaymentChannelClaim transaction][] with the `tfClose` flag set, or a [PaymentChannelFund transaction][] with the `Expiration` field set. _(9a in the [flow diagram][])_.
|
||||
|
||||
If the channel has no XRP remaining in it when the payer requests to close the channel, it closes immediately.
|
||||
|
||||
If the channel _does_ have XRP remaining, the request to close a channel acts as a final warning to the payee to redeem any outstanding claims right away. The payee has an amount of time no less than the settlement delay before the channel is closed. The exact number of seconds varies slightly based on the close times of ledgers.
|
||||
|
||||
The payee can also close a payment channel immediately after processing a claim _(9b in the [flow diagram][])_.
|
||||
|
||||
<!-- SPELLING_IGNORE: 9a, 9b -->
|
||||
|
||||
Example of [submitting a transaction](../../../../references/http-websocket-apis/public-api-methods/transaction-methods/submit.md#sign-and-submit-mode) requesting a channel to close:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "submit",
|
||||
"params": [{
|
||||
"secret": "s████████████████████████████",
|
||||
"tx_json": {
|
||||
"Account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"TransactionType": "PaymentChannelClaim",
|
||||
"Channel": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"Flags": 2147614720
|
||||
},
|
||||
"fee_mult_max": 1000
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
After the transaction is included in a validated ledger, either party can look up the currently-scheduled expiration of the channel using the [account_channels method][]. Be sure to specify `"ledger_index": "validated"` to get data from the latest validated ledger version.
|
||||
|
||||
Example `account_channels` response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"channels": [
|
||||
{
|
||||
"account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"amount": "100000000",
|
||||
"balance": "1000000",
|
||||
"channel_id": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"destination_tag": 20170428,
|
||||
"expiration": 547073182,
|
||||
"public_key": "aB44YfzW24VDEJQ2UuLPV2PvqcPCSoLnL7y5M1EzhdW4LnK5xMS3",
|
||||
"public_key_hex": "023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6",
|
||||
"settle_delay": 86400
|
||||
}
|
||||
],
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the `expiration` value 547073182 in [seconds since the Ripple Epoch][] maps to `2017-05-02T20:46:22Z`, so any claims not redeemed by that time are no longer valid.
|
||||
|
||||
## 10. Anyone can close the expired channel.
|
||||
|
||||
After the settlement delay has passed or the channel has reached its planned expiration time, the channel is expired. Any further transaction that would affect the channel can only close it, returning unclaimed XRP to the payer.
|
||||
|
||||
The channel can remain on the ledger in an expired state indefinitely. This is because the ledger cannot change except as the results of a transaction, so _someone_ must send a transaction to cause the expired channel to close. As long as the channel remains on the ledger, it counts as an object owned by the payer for purposes of the [owner reserve](../../../../concepts/accounts/reserves.md#owner-reserves).
|
||||
|
||||
Ripple recommends that the payer sends a second [PaymentChannelClaim transaction][] with the `tfClose` flag for this purpose. However, other accounts, even those not involved in the payment channel, can cause an expired channel to close.
|
||||
|
||||
The command to submit the transaction is the same as the previous example requesting channel expiration. (However, its resulting [auto-filled](../../../../references/protocol/transactions/common-fields.md#auto-fillable-fields) `Sequence` number, signature, and identifying hash are unique.)
|
||||
|
||||
Example of [submitting](../../../../references/http-websocket-apis/public-api-methods/transaction-methods/submit.md#sign-and-submit-mode) a transaction to close an expired channel:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "submit",
|
||||
"params": [{
|
||||
"secret": "s████████████████████████████",
|
||||
"tx_json": {
|
||||
"Account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"TransactionType": "PaymentChannelClaim",
|
||||
"Channel": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"Flags": 2147614720
|
||||
},
|
||||
"fee_mult_max": 1000
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
When the transaction has been included in a validated ledger, you can look at the metadata of the transaction to confirm that it deleted the channel and returned the XRP to the sender.
|
||||
|
||||
Example response from using the [tx method][] to look up the transaction from this step:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"Account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"Channel": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3",
|
||||
"Fee": "5606",
|
||||
"Flags": 2147614720,
|
||||
"Sequence": 41,
|
||||
"SigningPubKey": "023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6",
|
||||
"TransactionType": "PaymentChannelClaim",
|
||||
"TxnSignature": "3044022008922FEB6F7D35D42006685BCBB007103D2A40AFAA69A7CFC10DF529F94BB6A402205D67816F50BBAEE0A2709AA3A93707304EC21133550FD2FF7436AD0C3CA6CE27",
|
||||
"date": 547091262,
|
||||
"hash": "9C0CAAC3DD1A74461132DA4451F9E53BDF4C93DFDBEFCE1B10021EC569013B33",
|
||||
"inLedger": 29480670,
|
||||
"ledger_index": 29480670,
|
||||
"meta": {
|
||||
"AffectedNodes": [
|
||||
{
|
||||
"ModifiedNode": {
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"LedgerIndex": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
|
||||
"PreviousTxnID": "C9FE08FC88CF76C3B06622ADAA47AE99CABB3380E4D195E7751274CFD87910EB",
|
||||
"PreviousTxnLgrSeq": 29385089
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeletedNode": {
|
||||
"FinalFields": {
|
||||
"Account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"Amount": "100000000",
|
||||
"Balance": "1000000",
|
||||
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"DestinationTag": 20170428,
|
||||
"Expiration": 547073182,
|
||||
"Flags": 0,
|
||||
"OwnerNode": "0000000000000000",
|
||||
"PreviousTxnID": "C5C70B2BCC515165B7F62ACC8126F8F8B655EB6E1D949A49B2358262BDA986B4",
|
||||
"PreviousTxnLgrSeq": 29451256,
|
||||
"PublicKey": "023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6",
|
||||
"SettleDelay": 86400
|
||||
},
|
||||
"LedgerEntryType": "PayChannel",
|
||||
"LedgerIndex": "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ModifiedNode": {
|
||||
"FinalFields": {
|
||||
"Account": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"Balance": "1041862844",
|
||||
"Flags": 0,
|
||||
"OwnerCount": 2,
|
||||
"Sequence": 42
|
||||
},
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"LedgerIndex": "B1CB040A17F9469BC00376EC8719535655824AD16CB5F539DD5765FEA88FDBE3",
|
||||
"PreviousFields": {
|
||||
"Balance": "942868450",
|
||||
"OwnerCount": 3,
|
||||
"Sequence": 41
|
||||
},
|
||||
"PreviousTxnID": "C5C70B2BCC515165B7F62ACC8126F8F8B655EB6E1D949A49B2358262BDA986B4",
|
||||
"PreviousTxnLgrSeq": 29451256
|
||||
}
|
||||
},
|
||||
{
|
||||
"ModifiedNode": {
|
||||
"FinalFields": {
|
||||
"Flags": 0,
|
||||
"Owner": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
|
||||
"RootIndex": "E590FC40B4F24D18341569BD3702A2D4E07E7BC04D11CE63608B67979E67030C"
|
||||
},
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"LedgerIndex": "E590FC40B4F24D18341569BD3702A2D4E07E7BC04D11CE63608B67979E67030C"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TransactionIndex": 7,
|
||||
"TransactionResult": "tesSUCCESS"
|
||||
},
|
||||
"status": "success",
|
||||
"validated": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the transaction's metadata, look for the following:
|
||||
|
||||
- A `DeletedNode` entry with `"LedgerEntryType": "PayChannel"`. The `LedgerIndex` field should match the Channel ID. This indicates that the channel was deleted.
|
||||
- A `ModifiedNode` entry with `"LedgerEntryType": "AccountRoot"`. The change in the `Balance` field in `PreviousFields` and `FinalFields` reflects the unspent XRP being returned to the payer.
|
||||
|
||||
Those fields indicate that the payment channel is closed.
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
This concludes the tutorial of Payment Channel usage. Ripple encourages users to find unique and interesting use cases to take full advantage of the speed and convenience of payment channels.
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [What is XRP?](../../../../introduction/what-is-xrp.md)
|
||||
- [Payment Types](../../../../concepts/payment-types/index.md)
|
||||
- [Payment Channels](../../../../concepts/payment-types/payment-channels.md)
|
||||
- **Tutorials:**
|
||||
- [Send XRP](../../send-xrp.md)
|
||||
- [Look Up Transaction Results](../../../../concepts/transactions/finality-of-results/look-up-transaction-results.md)
|
||||
- [Reliable Transaction Submission](../../../../concepts/transactions/reliable-transaction-submission.md)
|
||||
- **References:**
|
||||
- [PaymentChannelClaim transaction][]
|
||||
- [PaymentChannelCreate transaction][]
|
||||
- [PaymentChannelFund transaction][]
|
||||
- [channel_authorize method][]
|
||||
- [channel_verify method][]
|
||||
- [PayChannel ledger object](../../../../references/protocol/ledger-data/ledger-entry-types/paychannel.md)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
html: open-a-payment-channel-to-enable-an-inter-exchange-network.html
|
||||
parent: use-payment-channels.html
|
||||
seo:
|
||||
description: As a digital asset exchange, use a payment channel to make more payments faster with fewer fees.
|
||||
labels:
|
||||
- Payment Channels
|
||||
---
|
||||
# Open a Payment Channel to Enable an Inter-Exchange Network
|
||||
|
||||
A payment channel enables you to send one-way, "asynchronous" XRP payments that can be divided into very small increments and settled later. As a digital asset exchange, if you send many payments of XRP to another exchange, you can improve the efficiency of these payments by opening an XRP Ledger [payment channel](../../../../concepts/payment-types/payment-channels.md) between your exchange (the _payer_ exchange) and the other exchange (the _payee_ exchange). In the case of a two-way flow with another exchange, you can open two payment channels (one for each direction).
|
||||
|
||||
|
||||
|
||||
## Why Send XRP to Other Exchanges?
|
||||
|
||||
The need to send XRP from your exchange to another exchange may originate with your customers withdrawing XRP from your exchange and depositing it to the other exchange. If you are a large exchange, you probably have many customers moving XRP from your exchange into another exchange. You may be processing XRP payments all day long and for each payment, you are waiting for confirmation times, potentially at both ends of the transaction, as well as paying transaction costs.
|
||||
|
||||
|
||||
|
||||
## Benefits of Using a Payment Channel
|
||||
|
||||
Here are some of the benefits of using a payment channel to send XRP instead of using individual payment transactions: <!-- {# TODO: for the future, complete https://ripplelabs.atlassian.net/browse/DOC-2243 to see if using a payment channel would actually result in a cost benefit to the sending exchange #}-->
|
||||
|
||||
- **Process withdrawals faster:** A standard payment transaction involves submitting an XRP Ledger transaction and waiting for a new ledger version that includes the transaction to be approved by [consensus](../../../../concepts/consensus-protocol/index.md). When you use a payment channel to send XRP, creation and verification of a claim, which guarantees the payment of XRP, all happen outside of the consensus process. This means that the payer exchange can guarantee XRP payments to the payee exchange at a rate limited only by the participants' ability to create and verify the digital signatures of the claims.
|
||||
|
||||
```
|
||||
For your customers who are moving XRP to take advantage of arbitrage opportunities or to do algorithmic trading, speed matters. Enabling a customer to move XRP and start trading in an instant is a compelling differentiator for your exchange.
|
||||
```
|
||||
|
||||
- **Connect to the Internet of Value:** One of the key requirements of the [Internet of Value](https://ripple.com/insights/the-internet-of-value-what-it-means-and-how-it-benefits-everyone/) is interoperability. The [Interledger Protocol](https://interledger.org/) (ILP), which plays a large role in driving this interoperability, works best when it [uses payment channels](https://interledger.org/rfcs/0027-interledger-protocol-4) as its method for rebalancing accounts. In effect, when you open a payment channel from your exchange to another, you are connecting to the Internet of Value and helping to create the inter-exchange network that is fundamental to the success of the Internet of Value and the apps that are built on it.
|
||||
|
||||
```
|
||||
Connecting your exchange to other exchanges by way of payment channels is another differentiator. For customers who are moving XRP to buy various currencies across exchanges, knowing that they can move XRP at a moment's notice from your exchange to any number of exchanges in the Internet of Value can make your exchange a preferred place to custody their assets.
|
||||
```
|
||||
|
||||
Here’s a roadmap to the high-level tasks you’ll need to perform to implement this payment channel use case. To go directly to a full payment channels tutorial, see [Use Payment Channels](index.md).
|
||||
|
||||
<!-- #{TODO: for the future: per Warren, it would be great to add diagrams for each step in the flow - showing claims and batch redemptions moving through the payment channel. Also, we have any recommendations around Payment Channel Managers, we can surface that here.}# -->
|
||||
|
||||
|
||||
|
||||
## Understand payment channels
|
||||
|
||||
Learn more about payment channels and whether they provide the features you need for your specific implementation.
|
||||
|
||||
[Understand payment channels >](../../../../concepts/payment-types/payment-channels.md)
|
||||
|
||||
|
||||
|
||||
## Payer and payee: Set up and run `rippled` servers
|
||||
|
||||
To use a payment channel to send and receive XRP, both the payer and payee exchanges must each have access to a `rippled` server that they can use to send transactions. If your exchange processes XRP withdrawals directly, you are probably already running a `rippled` server that you can use for this purpose.
|
||||
|
||||
If not, a great way for an exchange to get access to a `rippled` server is to set up and run one.
|
||||
|
||||
[Set up and run rippled servers >](../../../../infrastructure/installation/install-rippled-on-ubuntu.md)
|
||||
|
||||
|
||||
|
||||
## Payer and payee: Fund XRP Ledger accounts with enough XRP
|
||||
|
||||
If your exchange processes XRP deposits and withdrawals directly, you probably have existing funded XRP Ledger accounts that you can use for this purpose. Ensure that they are funded with enough XRP as described here.
|
||||
|
||||
Along these lines, there's a good chance that you are following industry best practices and have a cold account plus one or more hot accounts. Use the hot accounts for your payment channels.
|
||||
|
||||
- The payer exchange must have a funded XRP Ledger account to be used to send XRP to the payee exchange.
|
||||
|
||||
```
|
||||
Aside from the [base reserve](../../../../concepts/accounts/reserves.md) (10 XRP) and the [owner reserve](../../../../concepts/accounts/reserves.md#owner-reserves) of a payment channel (2 XRP), the account must also be able to set aside enough XRP in the payment channel to cover the intended number of transactions.
|
||||
|
||||
The payer exchange can always top-off the channel using the [PaymentChannelFund](../../../../references/protocol/transactions/types/paymentchannelfund.md) transaction if it runs out of XRP. However, topping-off requires an actual on-ledger transaction and confirmation, so it could take 4-5 seconds of processing time and ~10 drops of XRP to complete the top-off transaction. The more XRP the payer exchange pre-funds, the less often they need to top-off, so they can save some time and money by pre-funding more XRP.
|
||||
|
||||
However, if the payer exchange puts in more XRP than they need, they need to [close the payment channel](index.md#9-when-the-payer-and-payee-are-done-doing-business-the-payer-requests-for-the-channel-to-be-closed) to get the XRP back. This means waiting out the following events:
|
||||
|
||||
1. Completion of the payer's request to start closing the payment channel.
|
||||
2. Passage of the `SettleDelay` time set for the payment channel.
|
||||
3. Completion of a request to finish closing the payment channel after the `SettleDelay` has passed.
|
||||
```
|
||||
|
||||
- The payee exchange must have a funded XRP Ledger account to be used to redeem (receive) XRP sent by the payer exchange.
|
||||
|
||||
```
|
||||
The account needs at least 11 XRP, which provides the 10 XRP [base reserve](../../../../concepts/accounts/reserves.md), plus enough to pay the transaction costs of redeeming claims, which are trivial. For example, you could redeem thousands of claims for less than 1 XRP in total.
|
||||
```
|
||||
|
||||
[Fund XRP Ledger accounts with enough XRP >](../../../../concepts/accounts/accounts.md)
|
||||
|
||||
|
||||
## Payer: [Open a payment channel](index.md#1-the-payer-creates-a-payment-channel-to-a-particular-recipient)
|
||||
|
||||
The payer exchange opens a payment channel from their XRP Ledger account to the payee exchange's XRP Ledger account. Opening a payment channel includes setting certain specifics of the channel, such as its expiration date and the amount it can hold.
|
||||
|
||||
For this exchange use case, there is no real need to ever close the channel, so the payer exchange may not want to define a `CancelAfter` (expiration) value. If they ever need to close the channel, they can still do so.
|
||||
|
||||
As the payer exchange, you can think of the payment channel as a special sub-wallet exclusively for a particular destination. Consider estimating how much XRP the payment channel requires similar to how you would estimate a hot wallet's needs. According to [typical best practices](../../../../concepts/accounts/account-types.md), exchanges hold the vast majority of XRP across all of their user accounts in a cold wallet, with a small amount of XRP in a hot wallet. <!-- STYLE_OVERRIDE: wallet, hot wallet, cold wallet -->
|
||||
|
||||
Along these lines, you should also decide approximately how often you want to add more XRP to the payment channel---for example, daily, every 4 hours, or every 15 minutes---and estimate the volume of XRP that you send to the payee exchange during that interval. You should fund the payment channel with enough to cover at least that much volume or the largest withdrawal that you want to process without delay, whichever is larger. For example, if you plan to refill the channel every 15 minutes, have an average volume of 50 XRP every 15 minutes, but occasionally send transfers of 10,000 XRP, you should supply the channel with at least 10,000 XRP.
|
||||
|
||||
For withdrawals that are larger than the amount of XRP you have in the payment channel, you must process them specially. Either you can send large withdrawals as normal XRP payments, skipping the payment channel entirely, or you can first send a transaction to add the full withdrawal amount to the payment channel before creating claims for those. (See below for details on creating claims.)
|
||||
|
||||
If either exchange has multiple hot accounts in the XRP Ledger, the two exchanges should each choose a specific hot account to use with the payment channel between them. Although there can be other configurations, this use case assumes one payment channel connecting two exchanges. This channel can serve all customers sending XRP from the payer exchange to the payee exchange.
|
||||
|
||||
Since payment channels are unidirectional, you need a second channel in the opposite direction to send XRP from the _payee_ exchange to the _payer_ exchange. This second channel does not need to connect the exact same pair of hot accounts, but it is most convenient to do so. With two unidirectional channels, each exchange can use the XRP it redeems from its incoming channel to refill its outgoing channel.
|
||||
|
||||
|
||||
|
||||
|
||||
## Payee: Verify payment channel details
|
||||
|
||||
The payee exchange reviews the details of the payment channel.
|
||||
|
||||
[Verify payment channel details >](index.md#2-the-payee-checks-specifics-of-the-payment-channel)
|
||||
|
||||
|
||||
|
||||
## Payer: Create claims
|
||||
|
||||
The payer exchange creates one or more claims for amounts of XRP that it wants to guarantee to the payee exchange.
|
||||
|
||||
[Create claims >](index.md#3-the-payer-creates-one-or-more-signed-claims-for-the-xrp-in-the-channel)
|
||||
|
||||
|
||||
## Payer: Send claim details to the payer exchange
|
||||
|
||||
After creating a claim, the payer exchange must send details of the claim to the payee exchange, off-ledger.
|
||||
|
||||
Consider a series of claims prompted by payer exchange customers withdrawing XRP and depositing it to the payee exchange. In this case, the payer and payee exchanges should agree on the information the payer exchange must send for each claim to enable the payee exchange to correctly credit its customers' accounts. For example, consider sharing the following claim information off-ledger:
|
||||
|
||||
**Channel ID**: `7C02D0802B272599889ADFA4298FD92E4C8BD5120ED9A5BA3884CF636F9B4029`
|
||||
|
||||
**Public key**: `023D9BFCC22FB9A997E45ACA0D2D679A6A1AE5589E51546F3EDC4E9B16713FC255`
|
||||
|
||||
| Sequence | Signature | Amount | Destination Tag |
|
||||
|:---------|:--------------------|:-------|:----------------|
|
||||
| 1 | `3045022100CE6E...` | 2000 | 12345678 |
|
||||
| 2 | `304402200C304A...` | 3000 | 23456781 |
|
||||
| 3 | `30450221009849...` | 4000 | 34567812 |
|
||||
|
||||
| Claim Information | Purpose |
|
||||
|:--------------------|:-------------------------------------------------------|
|
||||
| **Channel ID** | Payment channel the payer exchange used to create the claim. The payee exchange needs this value to verify and redeem the claim. |
|
||||
| **Public key** | Public key the payer exchange used to open the payment channel. The payee exchange needs this value to verify and redeem the claim. |
|
||||
| **Sequence** | A value that indicates the sequence in which the payer exchange created the claims. The payee exchange needs this value to keep track of and redeem claims in the correct order. For example, if the payer exchange did not provide the sequence value and the payee exchange lost track of the second claim above, the payee exchange might incorrectly credit 2000 XRP to destination tag 34567812. If the payer exchange did provide the sequence value, the payee exchange would know that it needs to account for a claim between claim 1 and claim 3. With claim 2 accounted for, the payee exchange could correctly credit 1000 XRP to destination tag 23456781 and 1000 XRP to destination tag 34567812. |
|
||||
| **Signature** | Signature of the claim. The payee exchange needs this value to verify and redeem the claim. |
|
||||
| **Amount** | Cumulative amount of the claims created by the payer exchange. The payee exchange needs this value to verify and redeem the claim. For information about how to calculate the actual amount the payee exchange needs to credit the customer, see [Verify claims](#payee-verify-claims). |
|
||||
| **Destination Tag** | Destination tag of the customer account on the payee exchange that needs to be credited based on the claim. The payer exchange can get this value from their customer's withdrawal request, which should provide a destination tag for the deposit to the payee exchange. When the payee exchange redeems claims, the XRP is deposited into the payee exchange's XRP Ledger account. The payee exchange can then credit the XRP from the claim to the appropriate customer account based on the destination tag provided. |
|
||||
|
||||
[Send claim details to the payer exchange >](index.md#4-the-payer-sends-a-claim-to-the-payee-as-payment-for-goods-or-services)
|
||||
|
||||
|
||||
## Payee: Verify claims
|
||||
|
||||
The payee exchange verifies claims sent by the payer exchange.
|
||||
|
||||
After verifying claims, the payee exchange should credit the claimed XRP to the customer accounts indicated by the destination tags sent by the payer exchange. Because claim amounts are cumulative, the payee exchange needs to be careful to credit the customer for only the _the difference from the previous claim_.
|
||||
|
||||
For example, to know how much to credit a customer for a claim amount of 3000, the payee exchange needs to know that the previous claim amount was 2000. The difference between the claim amount and the previous claim amount (3000 - 2000 = 1000) is the amount the payee exchange must credit to the customer account.
|
||||
|
||||
[Verify claims >](index.md#5-the-payee-verifies-the-claims)
|
||||
|
||||
|
||||
|
||||
## Payee: Redeem them in batches
|
||||
|
||||
The payee exchange can redeem batches of claims after verifying them to receive the XRP guaranteed by the payer exchange. Here are some guidelines the payee exchange can use to decide how often to redeem claims:
|
||||
|
||||
- Don't redeem every claim. That's not gaining any benefit from the payment channels.
|
||||
|
||||
- Don't wait until you have more in claims than you're scared to lose. If something goes wrong and you miss your chance to redeem a claim, you don't get paid. If that happens and you have already credited one or more customers in your system, you could be in trouble. Those customers may have already traded the XRP for other cryptocurrencies and withdrawn them. That leaves you with more XRP owed in your system than you were holding for your customers, and it's too late to correct the balances of the customers whose deposits you didn't receive.
|
||||
|
||||
- If the payer requests to close the channel, you won't get paid if you don't redeem your claims before it finishes closing. The amount of time you have is based on the `SettleDelay`.
|
||||
|
||||
- If the channel was created with an immutable `CancelAfter` time, be sure to redeem all outstanding claims before that time.
|
||||
|
||||
- You can decide to redeem, for example, after a certain amount of time, after accumulating a certain amount of credit, or based on other criteria you care about, such as how much you trust the payer exchange. The safest strategy is probably based on a combination of these criteria.
|
||||
|
||||
<!-- #{ TODO: Talk to some active PayChan users like Coil people to get some more specific recommendations. But I imagine that settling every few minutes or even hours could make sense depending on how much the payee exchange trusts the payer exchange, how many transactions they do how rapidly, etc. }# -->
|
||||
|
||||
[Redeem them in batches >](index.md#8-when-ready-the-payee-redeems-a-claim-for-the-authorized-amount)
|
||||
|
||||
|
||||
|
||||
## Payer and payee: Continue to use the payment channel
|
||||
|
||||
Payer and payee exchanges can continue to send, verify, and redeem batches of claims as needed within the parameters set by the payment channel.
|
||||
|
||||
[Continue to use the payment channel >](index.md#7-repeat-steps-3-6-as-desired)
|
||||
|
||||
|
||||
## Payer: When it's time, make a request to close the payment channel
|
||||
|
||||
When the payer exchange and payee exchange are done using the payment channel, the payer exchange can make a request to close the payment channel.
|
||||
|
||||
[Close the payment channel >](index.md#9-when-the-payer-and-payee-are-done-doing-business-the-payer-requests-for-the-channel-to-be-closed)
|
||||
@@ -0,0 +1,288 @@
|
||||
---
|
||||
parent: use-tokens.html
|
||||
seo:
|
||||
description: Set up an Automated Market Maker (AMM)
|
||||
embed_xrpl_js: true
|
||||
status: not_enabled
|
||||
filters:
|
||||
- interactive_steps
|
||||
labels:
|
||||
- Decentralized Exchange
|
||||
- Tokens
|
||||
- AMM
|
||||
steps: ['Connect', 'Generate', 'Acquire tokens', 'Check for AMM', 'Look up AMMCreate cost', 'Create AMM', 'Check AMM info', 'Check trust lines']
|
||||
---
|
||||
# Create an Automated Market Maker
|
||||
|
||||
_(Requires the [AMM amendment][] {% not-enabled /%})_
|
||||
|
||||
An [Automated Market Maker (AMM)](../../../concepts/tokens/decentralized-exchange/automated-market-makers.md) can be an efficient way to facilitate exchanges between two assets while earning its liquidity providers passive income. This tutorial shows how to create an AMM for a given asset pair.
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script type="application/javascript" src="/js/tutorials/create-amm.js"></script>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You must have an XRP Ledger address and some XRP. For development and testing purposes, you can get these from a [Faucet](/resources/dev-tools/xrp-faucets).
|
||||
- You should be familiar with the Getting Started instructions for your preferred client library. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js/) **version 2.11.0 or later**. See [Get Started Using JavaScript](../../javascript/get-started.md) for setup steps.
|
||||
- You can also read along and use the interactive steps in your browser without any setup.
|
||||
- You should have a basic understanding of how [tokens](../../../concepts/tokens/index.md) work in the XRP Ledger.
|
||||
- You may want to read about [Automated Market Makers in the XRP Ledger](../../../concepts/tokens/decentralized-exchange/automated-market-makers.md) first.
|
||||
|
||||
|
||||
## Example Code
|
||||
|
||||
Complete sample code for all of the steps of these tutorials is available under the [MIT license](https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE).
|
||||
|
||||
- See [Code Samples: Create an AMM](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/create-amm/) in the source repository for this website.
|
||||
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Connect to the network
|
||||
|
||||
You must be connected to the network to query it and submit transactions. The following code shows how to connect to a public {{use_network}} server using a supported [client library](../../../references/client-libraries.md):
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/create-amm/js/connect.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
For this tutorial, click the following button to connect:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
### 2. Get credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address, a secret key, and some XRP. For development and testing purposes, you can get these on the [{{use_network}}](../../../concepts/networks-and-servers/parallel-networks.md) using the following interface:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/generate-step.md" /%}
|
||||
|
||||
When you're building production-ready software, you should use an existing account, and manage your keys using a [secure signing configuration](../../../concepts/transactions/secure-signing.md). The following code shows how to get a `Wallet` instance using either the faucet or a seed provided by environment variable:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/create-amm/js/create-amm.js" from="// Get credentials" before="// Acquire tokens" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
### 3. Select and acquire assets
|
||||
|
||||
As the creator of an AMM, you are also the first liquidity provider and you have to supply it with a starting pool of assets. Other users of the XRP Ledger can also become liquidity providers by supplying assets after the AMM exists. It's crucial to choose assets carefully because, as a liquidity provider for an AMM, you are supplying some amounts of both for users to swap between. If one of the AMM's assets becomes worthless, other users can use the AMM to trade for the other asset, leaving the AMM (and thus, its liquidity providers including you) holding only the worthless one. Technically, the AMM always holds some positive amount of both assets, but the amounts can be very small.
|
||||
|
||||
You can choose any pair of fungible assets in the XRP Ledger, including XRP or tokens, as long as they meet the [restrictions on AMM assets](../../../concepts/tokens/decentralized-exchange/automated-market-makers.md#restrictions-on-assets).
|
||||
|
||||
For each of the two assets, you need to know its currency code and issuer; as an exception, XRP has no issuer. For each of the assets, you must hold a balance of the asset (or _be_ the issuer). The following sample code acquires two assets, "TST" (which it buys using XRP) and "FOO" (which it receives from the issuer).
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/create-amm/js/create-amm.js" from="// Acquire tokens" before="// Check if AMM already exists" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
This tutorial includes some example code to issue FOO tokens from a second test address. This is not realistic for a production scenario, because tokens do not inherently have value, but it makes it possible to demonstrate creating a new AMM for a unique currency pair. In production, you would acquire a second token in some other way, such as making an off-ledger deposit with the [stablecoin issuer](../../../use-cases/tokenization/stablecoin-issuer.md), or buying it in the [decentralized exchange](../../../concepts/tokens/decentralized-exchange/index.md).
|
||||
|
||||
The helper function for issuing follows an abbreviated version of the steps in the [Issue a Fungible Token](issue-a-fungible-token.md) tutorial:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/create-amm/js/create-amm.js" from="/* Issue tokens" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Acquire tokens" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="buy-tst" class="btn btn-primary previous-steps-required">Buy TST</button>
|
||||
<button id="get-foo" class="btn btn-primary previous-steps-required">Get FOO</button>
|
||||
|
||||
{% loading-icon message="Working..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
### 4. Check if the AMM exists
|
||||
|
||||
Since there can only be one AMM for a specific pair of assets, it's best to check first before trying to create one. Use the [amm_info method][] to check whether the AMM already exists. For the request, you specify the two assets. The response should be an `actNotFound` error if the AMM does not exist.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/create-amm/js/create-amm.js" from="// Check if AMM already exists" before="// Look up AMM transaction cost" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
If the AMM does already exist, you should double-check that you specified the right pair of assets. If someone else has already created this AMM, you can deposit to it instead. <!-- TODO: link to a tutorial about depositing to and withdrawing from an AMM when one exists -->
|
||||
|
||||
{% interactive-block label="Check for AMM" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="check-for-amm" class="btn btn-primary previous-steps-required">Check AMM</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
### 5. Look up the AMMCreate transaction cost
|
||||
|
||||
Creating an AMM has a special [transaction cost][] to prevent spam: since it creates objects in the ledger that no one owns, you must burn at least one [owner reserve increment](../../../concepts/accounts/reserves.md) of XRP to send the AMMCreate transaction. The exact value can change due to [fee voting](https://xrpl.org/fee-voting.html), so you should look up the current incremental reserve value using the [server_state method][].
|
||||
|
||||
It is also a good practice to display this value and give a human operator a chance to stop before you send the transaction. Burning an owner reserve is typically a much higher cost than sending a normal transaction, so you don't want it to be a surprise. (Currently, on both Mainnet and Devnet, the cost of sending a typical transaction is 0.000010 XRP but the cost of AMMCreate is 2 XRP.)
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/create-amm/js/create-amm.js" from="// Look up AMM transaction cost" before="// Create AMM" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Look up AMMCreate cost" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="look-up-ammcreate-cost" class="btn btn-primary previous-steps-required">Check cost</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 6. Send AMMCreate transaction
|
||||
|
||||
Send an [AMMCreate transaction][] to create the AMM. Important aspects of this transaction include:
|
||||
|
||||
| Field | Value | Description |
|
||||
|-------|--------|-------------|
|
||||
| `Asset` | [Currency Amount][] | Starting amount of one asset to deposit in the AMM. |
|
||||
| `Asset2` | [Currency Amount][] | Starting amount of the other asset to deposit in the AMM. |
|
||||
| `TradingFee` | Number | The fee to charge when trading against this AMM instance. The maximum value is `1000`, meaning a 1% fee; the minimum value is `0`. If you set this too high, it may be too expensive for users to trade against the AMM; but the lower you set it, the more you expose yourself to currency risk from the AMM's assets changing in value relative to one another. |
|
||||
| `Fee` | String - XRP Amount | The transaction cost you looked up in a previous step. Client libraries may require that you add a special exception or reconfigure a setting to specify a `Fee` value this high. |
|
||||
|
||||
For the two starting assets, it does not matter which is `Asset` and which is `Asset2`, but you should specify amounts that are about equal in total value, because otherwise other users can profit at your expense by trading against the AMM.
|
||||
|
||||
**Tip:** Use `fail_hard` when submitting this transaction, so you don't have to pay the high transaction cost if the transaction initially fails. (It's still _possible_ that the transaction could tentatively succeed, and then fail and still burn the transaction cost, but this protects you from burning XRP on many of types of failures.)
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/create-amm/js/create-amm.js" from="// Create AMM" before="// Confirm that AMM exists" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Create AMM" steps=$frontmatter.steps %}
|
||||
|
||||
<form>
|
||||
<div class="form-group row">
|
||||
<label for="trading-fee" class="col-form-label col-sm-3">Trading Fee</label>
|
||||
<div class="input-group col">
|
||||
<input type="number" class="form-control" id="trading-fee" value="0.5" step="0.001" min="0" max="1" />
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="asset-amount" class="col-form-label col-sm-3">Amount</label>
|
||||
<div class="input-group col">
|
||||
<input type="number" class="form-control" id="asset-amount" value="15" step="any" min="0" />
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">TST.rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="asset2-amount" class="col-form-label col-sm-3">Amount2</label>
|
||||
<div class="input-group col">
|
||||
<input type="number" class="form-control" id="asset2-amount" value="100" step="any" min="0" />
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">FOO.<span class="foo-issuer">(issuer)</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button id="create-amm" class="btn btn-primary previous-steps-required">Create AMM</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
### 7. Check AMM info
|
||||
|
||||
If the AMMCreate transaction succeeded, it creates the AMM and related objects in the ledger. You _could_ check the metadata of the AMMCreate transaction, but it is often easier to call the [amm_info method][] again to get the status of the newly-created AMM.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/create-amm/js/create-amm.js" from="// Confirm that AMM exists" before="// Check token balances" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
In the result, the `amm` object's `lp_token` field is particularly useful because it includes the issuer and currency code of the AMM's LP Tokens, which you need to know for many other AMM-related transactions. LP Tokens always have a hex currency code starting with `03`, and the rest of the code is derived from the issuers and currency codes of the tokens in the AMM's pool. The issuer of the LP Tokens is the AMM address, which is randomly chosen when you create an AMM.
|
||||
|
||||
Initially, the AMM's total outstanding LP Tokens, reported in the `lp_token` field of the `amm_info` response, match the tokens you hold as its first liquidity provider. However, after other accounts deposit liquidity to the same AMM, the amount shown in `amm_info` updates to reflect the total issued to all liquidity providers. Since others can deposit at any time, even potentially in the same ledger version where the AMM was created, you shouldn't assume that this amount represents your personal LP Tokens balance.
|
||||
|
||||
{% interactive-block label="Check AMM info" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="check-amm-info" class="btn btn-primary previous-steps-required">Check AMM</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
### 8. Check trust lines
|
||||
|
||||
You can also use the [account_lines method][] to get an updated view of your token balances. Your balances should be decreased by the amounts you deposited, but you now have a balance of LP Tokens that you received from the AMM.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/create-amm/js/create-amm.js" from="// Check token balances" before="// Disconnect" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The `account_lines` response shows only the tokens held by the account you looked up (probably yours). If you have a lot of tokens, you may want to specify the AMM address as the `peer` in the request so you don't have to [paginate](../../../references/http-websocket-apis/api-conventions/markers-and-pagination.md) over multiple requests to find the AMM's LP Tokens. In this tutorial, your account probably only holds the three different tokens, so you can see all three in the same response.
|
||||
|
||||
**Tip:** If one of the assets in the AMM's pool is XRP, you need to call the [account_info method][] on your account to see the difference in your balance (the `Balance` field of the account object).
|
||||
|
||||
{% interactive-block label="Check trust lines" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="check-trust-lines" class="btn btn-primary previous-steps-required">Check trust lines</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
## Next Steps
|
||||
|
||||
At this point the AMM is up and running, and [trades in the DEX](trade-in-the-decentralized-exchange.md) automatically use this AMM in combination with Offers to achieve the best exchange rate possible between the two assets in the AMM's pool. If the flow of funds between the two assets is relatively balanced and there are no major shifts in the value of one asset compared to the other, this can become a source of passive income for you and anyone else who deposits liquidity into the AMM's pool.
|
||||
|
||||
When you want to withdraw liquidity from the AMM, you can use [AMMDeposit][] to cash in your LP Tokens to receive a share of the AMM's assets. You can also use LP Tokens like any other tokens in the XRP Ledger, which means you can trade them, use them in payments, or even deposit them in another AMM.
|
||||
|
||||
However, you should keep an eye on market conditions, and use tools like [AMMBid][] and [AMMVote][] to insulate yourself from losses due to changes in the relative value of the two assets in the pool.
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
202
docs/tutorials/tasks/use-tokens/enable-no-freeze.md
Normal file
202
docs/tutorials/tasks/use-tokens/enable-no-freeze.md
Normal file
@@ -0,0 +1,202 @@
|
||||
---
|
||||
html: enable-no-freeze.html
|
||||
parent: use-tokens.html
|
||||
seo:
|
||||
description: Permanently give up your account's ability to freeze tokens it issues.
|
||||
embed_xrpl_js: true
|
||||
filters:
|
||||
- interactive_steps
|
||||
labels:
|
||||
- Tokens
|
||||
steps: ['Generate', 'Connect', 'Send AccountSet', 'Wait', 'Confirm Settings']
|
||||
---
|
||||
# Enable No Freeze
|
||||
|
||||
If you [issue tokens](../../../concepts/tokens/index.md) in the XRP Ledger, can enable the [No Freeze setting](../../../concepts/tokens/fungible-tokens/freezes.md#no-freeze) to permanently limit your own ability to use the token freezing features of the XRP Ledger. (As a reminder, this only applies to issued tokens, not XRP.) This tutorial shows how to enable the No Freeze setting on your issuing account.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You need a connection to the XRP Ledger network. As shown in this tutorial, you can use public servers for testing.
|
||||
- You should be familiar with the Getting Started instructions for your preferred client library. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js/). See [Get Started Using JavaScript](../../javascript/get-started.md) for setup steps.
|
||||
- You don't need to have [issued a token](issue-a-fungible-token.md) in the XRP Ledger to enable No Freeze, but the main reason you would do so is if you intend to or have already issued such a token.
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script type="application/javascript" src="/js/tutorials/enable-no-freeze.js"></script>
|
||||
|
||||
|
||||
## Example Code
|
||||
|
||||
Complete sample code for all of the steps of this tutorial is available under the [MIT license](https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE).
|
||||
|
||||
- See [Code Samples: Freeze](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/freeze/) in the source repository for this website.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Get Credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address and secret key, and some XRP. If you use the best practice of having separate ["cold" and "hot" addresses](../../../concepts/accounts/account-types.md), you need the **master keys** to the _cold address_, which is the **issuer** of the token. Only the issuer's No Freeze setting has any effect on a token.
|
||||
|
||||
**Caution:** You cannot use a [regular key pair](../../../concepts/accounts/cryptographic-keys.md) or [multi-signing](../../../concepts/accounts/multi-signing.md) to enable the No Freeze setting.
|
||||
|
||||
For this tutorial, you can get credentials from the following interface:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/generate-step.md" /%}
|
||||
|
||||
When you're building production-ready software, you should use an existing account, and manage your keys using a [secure signing configuration](../../../concepts/transactions/secure-signing.md).
|
||||
|
||||
|
||||
### 2. Connect to the Network
|
||||
|
||||
You must be connected to the network to submit transactions to it. The following code shows how to connect to a public XRP Ledger Testnet server a supported [client library](../../../references/client-libraries.md):
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/get-started/js/base.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
For this tutorial, click the following button to connect:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
|
||||
### 3. Send AccountSet Transaction
|
||||
|
||||
To enable the No Freeze setting, send an [AccountSet transaction][] with a `SetFlag` field containing the [`asfNoFreeze` value (`6`)](../../../references/protocol/transactions/types/accountset.md#accountset-flags). To send the transaction, you first _prepare_ it to fill out all the necessary fields, then _sign_ it with your account's secret key, and finally _submit_ it to the network.
|
||||
|
||||
For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/freeze/js/set-no-freeze.js" from="// Submit an AccountSet transaction" before="// Done" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"id": 12,
|
||||
"command": "submit",
|
||||
"tx_json": {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"SetFlag": 6,
|
||||
"LastLedgerSequence": 18124917,
|
||||
"Sequence": 4
|
||||
},
|
||||
"secret": "s████████████████████████████"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
{% interactive-block label="Send AccountSet" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="send-accountset" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait">Send AccountSet</button>
|
||||
|
||||
{% loading-icon message="Sending" /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
|
||||
### 4. Wait for Validation
|
||||
|
||||
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. If the XRP Ledger is busy or poor network connectivity delays a transaction from being relayed throughout the network, a transaction may take longer to be confirmed. (For information on how to set an expiration for transactions, see [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).)
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" /%}
|
||||
|
||||
|
||||
### 5. Confirm Account Settings
|
||||
|
||||
After the transaction is validated, you can check your account's settings to confirm that the No Freeze flag is enabled. You can do this by calling the [account_info method][] and checking the value of the account's `Flags` field to see if the [`lsfNoFreeze` bit (`0x00200000`)](../../../references/protocol/ledger-data/ledger-entry-types/accountroot.md#accountroot-flags) is enabled.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/freeze/js/check-no-freeze.js" from="// Request account info" before="await client.disconnect()" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
Request:
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"command": "account_info",
|
||||
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"ledger_index": "validated"
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
{
|
||||
"id": 4,
|
||||
"status": "success",
|
||||
"type": "response",
|
||||
"result": {
|
||||
"account_data": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"AccountTxnID": "41320138CA9837B34E82B3B3D6FB1E581D5DE2F0A67B3D62B5B8A8C9C8D970D0",
|
||||
"Balance": "100258663",
|
||||
"Domain": "6D64756F31332E636F6D",
|
||||
"EmailHash": "98B4375E1D753E5B91627516F6D70977",
|
||||
"Flags": 12582912,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"MessageKey": "0000000000000000000000070000000300",
|
||||
"OwnerCount": 4,
|
||||
"PreviousTxnID": "41320138CA9837B34E82B3B3D6FB1E581D5DE2F0A67B3D62B5B8A8C9C8D970D0",
|
||||
"PreviousTxnLgrSeq": 18123095,
|
||||
"Sequence": 352,
|
||||
"TransferRate": 1004999999,
|
||||
"index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
|
||||
"urlgravatar": "http://www.gravatar.com/avatar/98b4375e1d753e5b91627516f6d70977"
|
||||
},
|
||||
"ledger_hash": "A777B05A293A73E511669B8A4A45A298FF89AD9C9394430023008DB4A6E7FDD5",
|
||||
"ledger_index": 18123249,
|
||||
"validated": true
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Confirm Settings" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="confirm-settings" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait">Confirm Settings</button>
|
||||
|
||||
{% loading-icon message="Sending" /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Freezing Issued Currencies](../../../concepts/tokens/fungible-tokens/freezes.md)
|
||||
- [Trust Lines](../../../concepts/tokens/fungible-tokens/index.md)
|
||||
- **Tutorials:**
|
||||
- [Enact Global Freeze](enact-global-freeze.md)
|
||||
- [Freeze a Trust Line](freeze-a-trust-line.md)
|
||||
- **References:**
|
||||
- [account_lines method][]
|
||||
- [account_info method][]
|
||||
- [AccountSet transaction][]
|
||||
- [TrustSet transaction][]
|
||||
- [AccountRoot Flags](../../../references/protocol/ledger-data/ledger-entry-types/accountroot.md#accountroot-flags)
|
||||
- [RippleState (trust line) Flags](../../../references/protocol/ledger-data/ledger-entry-types/ripplestate.md#ripplestate-flags)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
288
docs/tutorials/tasks/use-tokens/enact-global-freeze.md
Normal file
288
docs/tutorials/tasks/use-tokens/enact-global-freeze.md
Normal file
@@ -0,0 +1,288 @@
|
||||
---
|
||||
html: enact-global-freeze.html
|
||||
parent: use-tokens.html
|
||||
seo:
|
||||
description: Freeze all tokens issued by your address.
|
||||
embed_xrpl_js: true
|
||||
filters:
|
||||
- interactive_steps
|
||||
labels:
|
||||
- Tokens
|
||||
- Security
|
||||
steps: ['Generate', 'Connect', 'Send AccountSet (Start Freeze)', 'Wait', 'Confirm Settings', 'Send AccountSet (End Freeze)', 'Wait (again)', 'Confirm Settings (After Freeze)']
|
||||
---
|
||||
# Enact Global Freeze
|
||||
|
||||
If you [issue tokens](../../../concepts/tokens/index.md) in the XRP Ledger, can enact a [Global Freeze](../../../concepts/tokens/fungible-tokens/freezes.md#global-freeze) to prevent users from sending your tokens to each other and trading your token in the [decentralized exchange](../../../concepts/tokens/decentralized-exchange/index.md). This tutorial shows how to enact and end a Global Freeze. You might want to do this, for example, if you see signs of suspicious activity related to your issuing address in the ledger, or to off-ledger systems you use to manage your token. (For example, if your token is a stablecoin and you process withdrawals and deposits from the ledger, you may want to freeze your token while you investigate if you suspect your systems have been hacked.) You can later disable the Global Freeze setting unless you have also enabled the [No Freeze setting](../../../concepts/tokens/fungible-tokens/freezes.md#no-freeze).
|
||||
|
||||
**Tip:** As a reminder, freezes only apply to issued tokens, not XRP, and do not prevent users from sending the tokens _directly_ back to the issuer.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You need a connection to the XRP Ledger network. As shown in this tutorial, you can use public servers for testing.
|
||||
- You should be familiar with the Getting Started instructions for your preferred client library. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js/). See [Get Started Using JavaScript](../../javascript/get-started.md) for setup steps.
|
||||
- You don't need to have [issued a token](issue-a-fungible-token.md) in the XRP Ledger to enact a Global Freeze, but the main reason you would do so is if you have already issued such a token.
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script type="application/javascript" src="/js/tutorials/enact-global-freeze.js"></script>
|
||||
|
||||
## Example Code
|
||||
|
||||
Complete sample code for all of the steps of this tutorial is available under the [MIT license](https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE).
|
||||
|
||||
- See [Code Samples: Freeze](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/freeze/) in the source repository for this website.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Get Credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address and secret key, and some XRP. If you use the best practice of having separate ["cold" and "hot" addresses](../../../concepts/accounts/account-types.md), you need the keys to the _cold address_, which is the **issuer** of the token. Only the issuer's Global Freeze setting has any effect on a token.
|
||||
|
||||
**Tip:** Unlike the No Freeze setting, you _can_ enable and disable a Global Freeze using a [regular key pair](../../../concepts/accounts/cryptographic-keys.md) or [multi-signing](../../../concepts/accounts/multi-signing.md).
|
||||
|
||||
For this tutorial, you can get credentials from the following interface:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/generate-step.md" /%}
|
||||
|
||||
When you're building production-ready software, you should use an existing account, and manage your keys using a [secure signing configuration](../../../concepts/transactions/secure-signing.md).
|
||||
|
||||
|
||||
### 2. Connect to the Network
|
||||
|
||||
You must be connected to the network to submit transactions to it. The following code shows how to connect to a public XRP Ledger Testnet server a supported [client library](../../../references/client-libraries.md):
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/get-started/js/base.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
For this tutorial, click the following button to connect:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
|
||||
### 3. Send AccountSet Transaction to Start the Freeze
|
||||
|
||||
To enable the Global Freeze setting, send an [AccountSet transaction][] with a `SetFlag` field containing the [`asfGlobalFreeze` value (`7`)](../../../references/protocol/transactions/types/accountset.md#accountset-flags). To send the transaction, you first _prepare_ it to fill out all the necessary fields, then _sign_ it with your account's secret key, and finally _submit_ it to the network.
|
||||
|
||||
**Caution:** Enacting a global freeze affects _all tokens issued by the address._ Furthermore, if you use the No Freeze setting, you cannot undo this action.
|
||||
|
||||
For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/freeze/js/set-global-freeze.js" from="// Prepare an AccountSet" before="// Investigate" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"id": "example_enable_global_freeze",
|
||||
"command": "submit",
|
||||
"tx_json": {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"SetFlag": 7,
|
||||
"LastLedgerSequence": 18122753,
|
||||
"Sequence": 349
|
||||
},
|
||||
"secret": "s████████████████████████████"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Send AccountSet (Start Freeze)" steps=$frontmatter.steps %}
|
||||
|
||||
<button class="btn btn-primary previous-steps-required send-accountset" data-wait-step-name="Wait" data-action="start_freeze">Send AccountSet</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 4. Wait for Validation
|
||||
|
||||
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. If the XRP Ledger is busy or poor network connectivity delays a transaction from being relayed throughout the network, a transaction may take longer to be confirmed. (For information on how to set an expiration for transactions, see [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).)
|
||||
|
||||
{% interactive-block label="Wait" steps=$frontmatter.steps %}
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" /%}
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 5. Confirm Account Settings
|
||||
|
||||
After the transaction is validated, you can check your issuing account's settings to confirm that the Global Freeze flag is enabled. You can do this by calling the [account_info method][] and checking the value of the account's `Flags` field to see if the [`lsfGlobalFreeze` bit (`0x00400000`)](../../../references/protocol/ledger-data/ledger-entry-types/accountroot.md#accountroot-flags) is on.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/freeze/js/check-global-freeze.js" from="// Request account info" before="await client.disconnect()" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
Request:
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"command": "account_info",
|
||||
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"ledger_index": "validated"
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
{
|
||||
"id": 4,
|
||||
"status": "success",
|
||||
"type": "response",
|
||||
"result": {
|
||||
"account_data": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"AccountTxnID": "41320138CA9837B34E82B3B3D6FB1E581D5DE2F0A67B3D62B5B8A8C9C8D970D0",
|
||||
"Balance": "100258663",
|
||||
"Domain": "6D64756F31332E636F6D",
|
||||
"EmailHash": "98B4375E1D753E5B91627516F6D70977",
|
||||
"Flags": 12582912,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"MessageKey": "0000000000000000000000070000000300",
|
||||
"OwnerCount": 4,
|
||||
"PreviousTxnID": "41320138CA9837B34E82B3B3D6FB1E581D5DE2F0A67B3D62B5B8A8C9C8D970D0",
|
||||
"PreviousTxnLgrSeq": 18123095,
|
||||
"Sequence": 352,
|
||||
"TransferRate": 1004999999,
|
||||
"index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
|
||||
"urlgravatar": "http://www.gravatar.com/avatar/98b4375e1d753e5b91627516f6d70977"
|
||||
},
|
||||
"ledger_hash": "A777B05A293A73E511669B8A4A45A298FF89AD9C9394430023008DB4A6E7FDD5",
|
||||
"ledger_index": 18123249,
|
||||
"validated": true
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Confirm Settings" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="confirm-settings" class="btn btn-primary previous-steps-required">Confirm Settings</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### Intermission: While Frozen
|
||||
|
||||
At this point all token issued by your address are frozen. During this time, you may want to investigate the potential security breach or take a snapshot of the balances of your token, depending on your reasons for enacting the global freeze.
|
||||
|
||||
Keep in mind that while a token is frozen, it is still possible for the frozen token to be sent _directly to_ or _directly from_ the issuer, so you may still want to disable any systems you have that are configured to send such transactions, and you may want to track incoming transactions without processing them so that you can eventually process the legitimate ones.
|
||||
|
||||
If you use a [hot wallet or operational address](../../../concepts/accounts/account-types.md), it has no special status compared to other users, so it also cannot send and receive the frozen tokens except when dealing directly with the issuer. <!-- STYLE_OVERRIDE: wallet, hot wallet -->
|
||||
|
||||
If you use the [No Freeze setting](../../../concepts/tokens/fungible-tokens/freezes.md#no-freeze) then the Global Freeze continues forever. If you want to resume issuing tokens, you must create a new account and start over from there.
|
||||
|
||||
Otherwise, you can continue to the next step whenever you're ready.
|
||||
|
||||
|
||||
### 6. Send AccountSet Transaction to End the Freeze
|
||||
|
||||
To end the Global Freeze, send an [AccountSet transaction][] with a `ClearFlag` field containing the [`asfGlobalFreeze` value (`7`)](../../../references/protocol/transactions/types/accountset.md#accountset-flags). As always, you first _prepare_ the transaction, _sign_ it, and finally _submit_ it to the network.
|
||||
|
||||
For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/freeze/js/set-global-freeze.js" from="// Now we disable" before="// Global freeze disabled" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"id": "example_disable_global_freeze",
|
||||
"command": "submit",
|
||||
"tx_json": {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"ClearFlag": 7,
|
||||
"LastLedgerSequence": 18122788,
|
||||
"Sequence": 350
|
||||
},
|
||||
"secret": "s████████████████████████████"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Send AccountSet (End Freeze)" steps=$frontmatter.steps %}
|
||||
|
||||
<button class="btn btn-primary previous-steps-required send-accountset" data-wait-step-name="Wait (again)" data-action="end_freeze">Send AccountSet (end the freeze)</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 7. Wait for Validation
|
||||
|
||||
As before, wait for the previous transaction to be validated by consensus before continuing.
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" variables={label: "Wait (again)"} /%}
|
||||
|
||||
|
||||
### 8. Confirm Account Settings
|
||||
|
||||
After the transaction is validated, you can confirm the status of the Global Freeze flag in the same way as before: by calling the [account_info method][] and checking the value of the account's `Flags` field to see if the [`lsfGlobalFreeze` bit (`0x00400000`)](../../../references/protocol/ledger-data/ledger-entry-types/accountroot.md#accountroot-flags) is **off**.
|
||||
|
||||
{% interactive-block label="Confirm Settings (After Freeze)" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="confirm-settings-end" class="btn btn-primary previous-steps-required">Confirm Settings (After Freeze)</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Freezing Issued Currencies](../../../concepts/tokens/fungible-tokens/freezes.md)
|
||||
- [Trust Lines](../../../concepts/tokens/fungible-tokens/index.md)
|
||||
- **Tutorials:**
|
||||
- [Enable No Freeze](enable-no-freeze.md)
|
||||
- [Freeze a Trust Line](freeze-a-trust-line.md)
|
||||
- [Change or Remove a Regular Key Pair](../manage-account-settings/change-or-remove-a-regular-key-pair.md)
|
||||
- **References:**
|
||||
- [account_lines method][]
|
||||
- [account_info method][]
|
||||
- [AccountSet transaction][]
|
||||
- [TrustSet transaction][]
|
||||
- [AccountRoot Flags](../../../references/protocol/ledger-data/ledger-entry-types/accountroot.md#accountroot-flags)
|
||||
- [RippleState (trust line) Flags](../../../references/protocol/ledger-data/ledger-entry-types/ripplestate.md#ripplestate-flags)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
359
docs/tutorials/tasks/use-tokens/freeze-a-trust-line.md
Normal file
359
docs/tutorials/tasks/use-tokens/freeze-a-trust-line.md
Normal file
@@ -0,0 +1,359 @@
|
||||
---
|
||||
html: freeze-a-trust-line.html
|
||||
parent: use-tokens.html
|
||||
seo:
|
||||
description: Freeze an individual holder of a token.
|
||||
embed_xrpl_js: true
|
||||
filters:
|
||||
- interactive_steps
|
||||
labels:
|
||||
- Tokens
|
||||
- Security
|
||||
steps: ['Generate', 'Connect', 'Choose Trust Line', 'Send TrustSet to Freeze', 'Wait', 'Check Freeze Status', 'Send TrustSet to End Freeze', 'Wait (again)']
|
||||
---
|
||||
# Freeze a Trust Line
|
||||
|
||||
This tutorial shows the steps to [freeze an individual trust line](../../../concepts/tokens/fungible-tokens/freezes.md#individual-freeze). The issuer of a token in the XRP Ledger may freeze the trust line to a particular counterparty if that account is engaged in suspicious activity.
|
||||
|
||||
**Tip:** As a reminder, freezes only apply to issued tokens, not XRP.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You need a connection to the XRP Ledger network. As shown in this tutorial, you can use public servers for testing.
|
||||
- You should be familiar with the Getting Started instructions for your preferred client library. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js/). See [Get Started Using JavaScript](../../javascript/get-started.md) for setup steps.
|
||||
- This tutorial assumes **you have already [issued a token](issue-a-fungible-token.md)** in the XRP Ledger.
|
||||
- You **cannot** have enabled the [No Freeze setting](../../../concepts/tokens/fungible-tokens/freezes.md#no-freeze), which gives up your ability to freeze individual trust lines.
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script type="application/javascript" src="/js/tutorials/freeze-individual-line.js"></script>
|
||||
|
||||
|
||||
## Example Code
|
||||
|
||||
Complete sample code for all of the steps of this tutorial is available under the [MIT license](https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE).
|
||||
|
||||
- See [Code Samples: Freeze](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/freeze/) in the source repository for this website.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Get Credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address and secret key, and some XRP. If you use the best practice of having separate ["cold" and "hot" addresses](../../../concepts/accounts/account-types.md), you need the keys to the _cold address_, which is the **issuer** of the token.
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/generate-step.md" /%}
|
||||
|
||||
### 2. Connect to the Network
|
||||
|
||||
You must be connected to the network to submit transactions to it. The following code shows how to connect to a public XRP Ledger Testnet server a supported [client library](/docs/references/client-libraries.md):
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/get-started/js/base.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```
|
||||
(Connect to wss:// URL of an XRP Ledger server using your preferred client.)
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
For purposes of this tutorial, use the following interface to connect and perform setup:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
|
||||
### 3. Choose Trust Line
|
||||
|
||||
You can only freeze one trust line per transaction, so you need to know which trust line you want. Each of your trust lines is uniquely identified by these 3 things:
|
||||
|
||||
- Your own address.
|
||||
- The address of the account linked to yours via the trust line.
|
||||
- The currency code of the trust line.
|
||||
|
||||
There can be multiple [trust lines](../../../concepts/tokens/fungible-tokens/index.md) between two accounts, each for a different currency. If you suspect a particular account is behaving maliciously, you may want to freeze all the trust lines between your accounts, one at a time. Use the [account_lines method][] with a pair of accounts to find all trust lines between those accounts, then choose a trust line to freeze from among the results. For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/freeze/js/set-individual-freeze.js" from="// Look up current trust lines" before="// Send a TrustSet" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
Example Request:
|
||||
|
||||
{
|
||||
"id": "example_look_up_trust_lines",
|
||||
"command": "account_lines",
|
||||
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"peer": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"ledger_index": "validated"
|
||||
}
|
||||
|
||||
// Example Response:
|
||||
|
||||
{
|
||||
"id": "example_look_up_trust_lines",
|
||||
"result": {
|
||||
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"ledger_hash": "AF5C6E6FBC44183D8662C7F5BF88D52F99738A0E66FF07FC7B5A516AC8EA1B37",
|
||||
"ledger_index": 67268474,
|
||||
"lines": [
|
||||
{
|
||||
"account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"balance": "0",
|
||||
"currency": "USD",
|
||||
"limit": "0",
|
||||
"limit_peer": "110",
|
||||
"quality_in": 0,
|
||||
"quality_out": 0
|
||||
}
|
||||
],
|
||||
"validated": true
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
For purposes of this tutorial, a second test address has created a trust line to the test address for the currency "FOO", which you can see in the following example:
|
||||
|
||||
{% interactive-block label="Choose Trust Line" steps=$frontmatter.steps %}
|
||||
|
||||
<div class="loader collapse" id="trust-line-setup-loader"><img class="throbber" src="/img/xrp-loader-96.png">Waiting for setup to complete...</div>
|
||||
<input type="hidden" id="peer-seed" value="" />
|
||||
<button id="look-up-trust-lines" class="btn btn-primary" disabled="disabled" title="Wait for setup to complete...">Choose Trust Line</button>
|
||||
<div class="loader loader-looking collapse"><img class="throbber" src="/img/xrp-loader-96.png">Looking...</div>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 4. Send TrustSet Transaction to Freeze the Trust Line
|
||||
|
||||
To enable or disable an Individual Freeze on a specific trust line, send a [TrustSet transaction][] with the [`tfSetFreeze` flag enabled](../../../references/protocol/transactions/types/trustset.md#trustset-flags). The fields of the transaction should be as follows:
|
||||
|
||||
| Field | Value | Description |
|
||||
|--------------------------|--------|-------------|
|
||||
| `Account` | String | Your issuing account's address. |
|
||||
| `TransactionType` | String | `TrustSet` |
|
||||
| `LimitAmount` | Object | Object defining the trust line to freeze. |
|
||||
| `LimitAmount`.`currency` | String | Currency of the trust line (cannot be XRP) |
|
||||
| `LimitAmount`.`issuer` | String | The XRP Ledger address of the counterparty to freeze |
|
||||
| `LimitAmount`.`value` | String | The amount of currency you trust this counterparty to issue to you, as a quoted number. As an issuer, this is typically `"0"`. |
|
||||
| `Flags` | Number | To enable a freeze, turn on the `tfSetFreeze` bit (`0x00100000`). |
|
||||
|
||||
As always, to send a transaction, you _prepare_ it by filling in all the necessary fields, _sign_ it with your cryptographic keys, and _submit_ it to the network. For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/freeze/js/set-individual-freeze.js" from="// Send a TrustSet" before="// Investigate" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"id": "example_freeze_individual_line",
|
||||
"command": "submit",
|
||||
"tx_json": {
|
||||
"TransactionType": "TrustSet",
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "12",
|
||||
"Flags": 1048576,
|
||||
"LastLedgerSequence": 18103014,
|
||||
"LimitAmount": {
|
||||
"currency": "USD",
|
||||
"issuer": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"value": "0"
|
||||
},
|
||||
"Sequence": 340
|
||||
},
|
||||
"secret": "s████████████████████████████"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Send TrustSet to Freeze" steps=$frontmatter.steps %}
|
||||
|
||||
<button class="btn btn-primary previous-steps-required send-trustset" data-wait-step-name="Wait" data-action="start_freeze">Send TrustSet (Freeze)</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
**Note:** If you want to freeze multiple trust lines in different currencies with the same counterparty, repeat this step for each trust line. It is possible to send several transactions in a single ledger if you use a different [sequence number](../../../references/protocol/data-types/basic-data-types.md#account-sequence) for each transaction. <!--{# TODO: link rapid/batch submission guidelines when https://github.com/XRPLF/xrpl-dev-portal/issues/1025 is done #}-->
|
||||
|
||||
|
||||
### 5. Wait for Validation
|
||||
|
||||
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. If the XRP Ledger is busy or poor network connectivity delays a transaction from being relayed throughout the network, a transaction may take longer to be confirmed. (For information on how to set an expiration for transactions, see [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).)
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" /%}
|
||||
|
||||
### 6. Check Trust Line Freeze Status
|
||||
|
||||
At this point, the trust line from the counterparty should be frozen. You can check the freeze status of any trust line using the [account_lines method][] with the following fields:
|
||||
|
||||
| Field | Value | Description |
|
||||
|:----------|:-------|:---------------------------------------------------|
|
||||
| `account` | String | Your address. (In this case, the issuing address.) |
|
||||
| `peer` | String | The address of the counterparty. |
|
||||
|
||||
**Caution:** The response includes _all_ trust lines between the two accounts. (Each different currency code uses a different trust line.) Be sure to check the one for the right token.
|
||||
|
||||
In the response, the field `"freeze": true` indicates that the account from the request has enabled an Individual Freeze on that trust line. The field `"freeze_peer": true` indicates that the counterparty (`peer`) from the request has frozen the trust line. For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/freeze/js/set-individual-freeze.js" from="// Confirm trust line status" before="// Investigate" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
Example Request:
|
||||
|
||||
{
|
||||
"id": "example_check_individual_freeze",
|
||||
"command": "account_lines",
|
||||
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"ledger_index": "validated",
|
||||
"peer": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"
|
||||
}
|
||||
|
||||
Example Response:
|
||||
|
||||
{
|
||||
"id": "example_check_individual_freeze",
|
||||
"status": "success",
|
||||
"type": "response",
|
||||
"result": {
|
||||
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"lines": [
|
||||
{
|
||||
"account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"balance": "10",
|
||||
"currency": "USD",
|
||||
"freeze": true,
|
||||
"limit": "0",
|
||||
"limit_peer": "110",
|
||||
"quality_in": 0,
|
||||
"quality_out": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Check Freeze Status" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="confirm-settings" class="btn btn-primary previous-steps-required">Check Trust Line</button>
|
||||
|
||||
{% loading-icon message="Checking..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 7. (Optional) Send TrustSet Transaction to End the Freeze
|
||||
|
||||
If you decide that the trust line no longer needs to be frozen (for example, you investigated and decided that the suspicious activity was benign), you can end the individual freeze in almost the same way that you froze the trust line in the first place. To end an individual freeze, send a [TrustSet transaction][] with the [`tfClearFreeze` flag enabled](../../../references/protocol/transactions/types/trustset.md#trustset-flags). The other fields of the transaction should be the same as when you froze the trust line:
|
||||
|
||||
| Field | Value | Description |
|
||||
|--------------------------|--------|-------------|
|
||||
| `Account` | String | Your issuing account's address. |
|
||||
| `TransactionType` | String | `TrustSet` |
|
||||
| `LimitAmount` | Object | Object defining the trust line to unfreeze. |
|
||||
| `LimitAmount`.`currency` | String | Currency of the trust line (cannot be XRP) |
|
||||
| `LimitAmount`.`issuer` | String | The XRP Ledger address of the counterparty to unfreeze |
|
||||
| `LimitAmount`.`value` | String | The amount of currency you trust this counterparty to issue to you, as a quoted number. As an issuer, this is typically `"0"`. |
|
||||
| `Flags` | Number | To end an individual freeze, turn on the `tfClearFreeze` bit (`0x00200000`) |
|
||||
|
||||
As always, to send a transaction, you _prepare_ it by filling in all the necessary fields, _sign_ it with your cryptographic keys, and _submit_ it to the network. For example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/freeze/js/set-individual-freeze.js" from="// Clear the individual" before="// End main" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"id": "example_end_individual_freeze",
|
||||
"command": "submit",
|
||||
"tx_json": {
|
||||
"TransactionType": "TrustSet",
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "12",
|
||||
"Flags": 2097152,
|
||||
"LastLedgerSequence": 18105115,
|
||||
"LimitAmount": {
|
||||
"currency": "USD",
|
||||
"issuer": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||
"value": "0"
|
||||
},
|
||||
"Sequence": 341
|
||||
},
|
||||
"secret": "s████████████████████████████"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Send TrustSet to End Freeze" steps=$frontmatter.steps %}
|
||||
|
||||
<button class="btn btn-primary previous-steps-required send-trustset" data-wait-step-name="Wait (again)" data-action="end_freeze">Send TrustSet (End Freeze)</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 8. Wait for Validation
|
||||
|
||||
As before, wait for the transaction to be validated by consensus.
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" variables={label: "Wait (again)"} /%}
|
||||
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Freezing Issued Currencies](../../../concepts/tokens/fungible-tokens/freezes.md)
|
||||
- [Trust Lines](../../../concepts/tokens/fungible-tokens/index.md)
|
||||
- **Tutorials:**
|
||||
- [Enable No Freeze](enable-no-freeze.md)
|
||||
- [Enact Global Freeze](enact-global-freeze.md)
|
||||
- [Change or Remove a Regular Key Pair](../manage-account-settings/change-or-remove-a-regular-key-pair.md)
|
||||
- **References:**
|
||||
- [account_lines method][]
|
||||
- [account_info method][]
|
||||
- [AccountSet transaction][]
|
||||
- [TrustSet transaction][]
|
||||
- [AccountRoot Flags](../../../references/protocol/ledger-data/ledger-entry-types/accountroot.md#accountroot-flags)
|
||||
- [RippleState (trust line) Flags](../../../references/protocol/ledger-data/ledger-entry-types/ripplestate.md#ripplestate-flags)
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
520
docs/tutorials/tasks/use-tokens/issue-a-fungible-token.md
Normal file
520
docs/tutorials/tasks/use-tokens/issue-a-fungible-token.md
Normal file
@@ -0,0 +1,520 @@
|
||||
---
|
||||
html: issue-a-fungible-token.html
|
||||
parent: use-tokens.html
|
||||
seo:
|
||||
description: Create your own token and issue it on the XRP Ledger Testnet.
|
||||
embed_xrpl_js: true
|
||||
filters:
|
||||
- interactive_steps
|
||||
labels:
|
||||
- Tokens
|
||||
steps: ['Generate', 'Connect', 'Configure Issuer', 'Wait (Issuer Setup)', 'Configure Hot Address', 'Wait (Hot Address Setup)', 'Make Trust Line', 'Wait (TrustSet)', 'Send Token', 'Wait (Payment)', 'Confirm Balances']
|
||||
---
|
||||
# Issue a Fungible Token
|
||||
|
||||
Anyone can issue various types of tokens in the XRP Ledger, ranging from informal "IOUs" to fiat-backed stablecoins, purely digital fungible and semi-fungible tokens, and more. This tutorial shows the technical steps of creating a token in the ledger. For more information on how XRP Ledger tokens work, see [Issued Currencies](../../../concepts/tokens/index.md); for more on the business decisions involved in issuing a stablecoin, see [Stablecoin Issuer](../../../use-cases/tokenization/stablecoin-issuer.md). <!-- STYLE_OVERRIDE: ious -->
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You need two funded XRP Ledger accounts, each with an address, secret key, and some XRP. For this tutorial, you can generate new test credentials as needed.
|
||||
- Each address needs enough XRP to satisfy the [reserve requirement](../../../concepts/accounts/reserves.md) including the additional reserve for a trust line.
|
||||
- You need a connection to the XRP Ledger network. As shown in this tutorial, you can use public servers for testing.
|
||||
- You should be familiar with the Getting Started instructions for your preferred client library. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js/). See [Get Started Using JavaScript](../../javascript/get-started.md) for setup steps.
|
||||
- **Python** with the [`xrpl-py` library](https://xrpl-py.readthedocs.io/). See [Get Started using Python](../../python/get-started.md) for setup steps.
|
||||
- **Java** with the [xrpl4j library](https://github.com/XRPLF/xrpl4j). See [Get Started Using Java](../../java/get-started.md) for setup steps.
|
||||
- You can also read along and use the interactive steps in your browser without any setup.
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script type="application/javascript" src="/js/tutorials/issue-a-token.js"></script>
|
||||
|
||||
## Example Code
|
||||
|
||||
Complete sample code for all of the steps of these tutorials is available under the [MIT license](https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE).
|
||||
|
||||
- See [Code Samples: Issue a Fungible Token](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/issue-a-token/) in the source repository for this website.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Get Credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address and secret key, and some XRP. You also need one or more recipients who are willing to hold the tokens you issue: unlike in some other blockchains, in the XRP Ledger you cannot force someone to hold a token they do not want.
|
||||
|
||||
The best practice is to use ["cold" and "hot" addresses](../../../concepts/accounts/account-types.md). The cold address is the **issuer** of the token. The hot address is like a regular user's address that you control. It receives tokens from the cold address, which you can then transfer to other users. A hot address is not strictly necessary, since you could send tokens directly to users from the cold address, but it is good practice for security reasons. In production, you should take extra care of the cold address's cryptographic keys (for example, keeping them offline) because it is much harder to replace a cold address than a hot address.
|
||||
|
||||
In this tutorial, the hot address receives the tokens you issue from the cold address. You can get the keys for two addresses using the following interface.
|
||||
|
||||
<!-- Special version of generate-step.md for getting sender AND receiver credentials -->
|
||||
|
||||
{% interactive-block label="Generate" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="generate-2x-creds-button" class="btn btn-primary" data-fauceturl="https://faucet.altnet.rippletest.net/accounts">Get Testnet credentials</button>
|
||||
|
||||
{% loading-icon message="Generating Keys..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
**Caution:** Ripple provides the [Testnet and Devnet](../../../concepts/networks-and-servers/parallel-networks.md) for testing purposes only, and sometimes resets the state of these test networks along with all balances. As a precaution, **do not** use the same addresses on Testnet/Devnet and Mainnet.
|
||||
|
||||
When you're building production-ready software, you should use an existing account, and manage your keys using a [secure signing configuration](../../../concepts/transactions/secure-signing.md).
|
||||
|
||||
|
||||
### 2. Connect to the Network
|
||||
|
||||
You must be connected to the network to submit transactions to it. The following code shows how to connect to a public XRP Ledger Testnet server with a supported [client library](../../../references/client-libraries.md):
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/get-started/js/base.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/py/issue-a-token.py" from="# Connect" before="# Get credentials" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/java/IssueToken.java" from="// Construct a network client" before="// Create cold" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
**Note:** The JavaScript code samples in this tutorial use the [`async`/`await` pattern](https://javascript.info/async-await). Since `await` needs to be used from within an `async` function, the remaining code samples are written to continue inside the `main()` function started here. You can also use Promise methods `.then()` and `.catch()` instead of `async`/`await` if you prefer.
|
||||
|
||||
For this tutorial, click the following button to connect:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
|
||||
### 3. Configure Issuer Settings
|
||||
|
||||
First, configure the settings for your cold address (which will become the issuer of your token). Most settings can be reconfigured later, with the following exceptions: <!-- STYLE_OVERRIDE: will -->
|
||||
|
||||
- [Default Ripple][]: **This setting is required** so that users can send your token to each other. It's best to enable it _before_ setting up any trust lines or issuing any tokens.
|
||||
- [Authorized Trust Lines][]: (Optional) This setting (also called "Require Auth") limits your tokens to being held _only_ by accounts you've explicitly approved. You cannot enable this setting if you already have any trust lines or offers for _any_ token.
|
||||
**Note:** To use authorized trust lines, you must perform additional steps that are not shown in this tutorial.
|
||||
|
||||
[Default Ripple]: ../../../concepts/tokens/fungible-tokens/rippling.md
|
||||
[Authorized Trust Lines]: ../../../concepts/tokens/fungible-tokens/authorized-trust-lines.md
|
||||
|
||||
Other settings you may want to, optionally, configure for your cold address (issuer):
|
||||
|
||||
| Setting | Recommended Value | Summary |
|
||||
|:-----------------------------|:--------------------|:------------------------|
|
||||
| [Require Destination Tags][] | Enabled or Disabled | Enable if you process withdrawals of your token to outside systems. (For example, your token is a stablecoin.) |
|
||||
| Disallow XRP | Enabled or Disabled | Enable if this address isn't meant to process XRP payments. |
|
||||
| [Transfer Fee][] | 0–1% | Charge a percentage fee when users send your token to each other. |
|
||||
| [Tick Size][] | 5 | Limit the number of decimal places in exchange rates for your token in the [decentralized exchange](../../../concepts/tokens/decentralized-exchange/index.md). A tick size of 5-6 reduces churn of almost-equivalent offers and speeds up price discovery compared to the default of 15. |
|
||||
| [Domain][] | (Your domain name) | Set to a domain you own so can [verify ownership of the accounts](../../../references/xrp-ledger-toml.md#account-verification). This can help reduce confusion or impersonation attempts. |
|
||||
|
||||
[Require Destination Tags]: ../manage-account-settings/require-destination-tags.md
|
||||
[Transfer Fee]: ../../../concepts/tokens/transfer-fees.md
|
||||
[Tick Size]: ../../../concepts/tokens/decentralized-exchange/ticksize.md
|
||||
[Domain]: ../../../references/protocol/transactions/types/accountset.md#domain
|
||||
|
||||
You can change these settings later as well.
|
||||
|
||||
**Note:** Many issuing settings apply equally to all tokens issued by an address, regardless of the currency code. If you want to issue multiple types of tokens in the XRP Ledger with different settings, you should use a different address to issue each different token.
|
||||
|
||||
The following code sample shows how to send an [AccountSet transaction][] to enable the recommended cold address settings:
|
||||
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/js/issue-a-token.js" from="// Configure issuer" before="// Configure hot" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/py/issue-a-token.py" from="# Configure issuer" before="# Configure hot" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/java/IssueToken.java" from="// Configure issuer" before="// Configure hot" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Configure Issuer" steps=$frontmatter.steps %}
|
||||
|
||||
<form>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="cold-default-ripple" disabled="disabled" checked title="The issuer MUST enable Default Ripple first." />
|
||||
<label for="cold-default-ripple" class="col-form-label" title="The issuer MUST enable Default Ripple first.">Default Ripple</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="cold-require-dest" />
|
||||
<label for="cold-require-dest" class="col-form-label">Require Destination Tags</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="cold-disallow-xrp" checked />
|
||||
<label for="cold-disallow-xrp" class="col-form-label">Disallow XRP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="cold-transfer-fee" class="col-form-label col-sm-3">Transfer Fee</label>
|
||||
<div class="input-group col">
|
||||
<input type="number" class="form-control" id="cold-transfer-fee" value="0" step="0.0000001" min="0" max="100" />
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="cold-tick-size" class="col-form-label col-sm-3">Tick Size</label>
|
||||
<div class="input-group col">
|
||||
<input type="number" class="form-control" id="cold-tick-size" value="5" step="1" min="0" max="15" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="cold-domain-text" class="col-form-label col-sm-3">Domain</label>
|
||||
<div class="input-group col">
|
||||
<div>
|
||||
<input type="text" class="form-control" value="example.com" pattern="([a-z0-9][a-z0-9\-]{0,62}[.])+[a-z]{2,6}" id="cold-domain-text" title="lower-case domain name of the account owner" />
|
||||
<small class="form-text">(text)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group col">
|
||||
<div class="col">
|
||||
<label class="form-control-plaintext" id="cold-domain-hex" for="cold-domain-text">6578616D706C652E636F6D</label>
|
||||
<small class="form-text">(hexadecimal)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button id="config-issuer-button" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait (Issuer Setup)">Configure issuer</button>
|
||||
|
||||
{% loading-icon message="Sending transaction..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
### 4. Wait for Validation
|
||||
|
||||
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. You should wait for your earlier transactions to be fully validated before proceeding to the later steps, to avoid unexpected failures from things executing out of order. For more information, see [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
The code samples in this tutorial use helper functions to wait for validation when submitting a transaction:
|
||||
|
||||
- **JavaScript:** The `submit_and_verify()` function, as defined in the [submit-and-verify code sample](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/submit-and-verify).
|
||||
- **Python:** The `submit_and_wait()` [method of the xrpl-py library](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.submit_and_wait).
|
||||
- **Java:** The `submitAndWaitForValidation()` method in the [sample Java class](https://github.com/XRPLF/xrpl-dev-portal/blob/master/_code-samples/issue-a-token/java/IssueToken.java).
|
||||
|
||||
**Tip:** Technically, you can configure the hot address in parallel with configuring the issuer address. For simplicity, this tutorial waits for each transaction one at a time.
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" variables={label: "Wait (Issuer Setup)"} /%}
|
||||
|
||||
|
||||
### 5. Configure Hot Address Settings
|
||||
|
||||
The hot address does not strictly require any settings changes from the default, but the following are recommended as best practices:
|
||||
|
||||
| Setting | Recommended Value | Summary |
|
||||
|:-----------------------------|:--------------------|:------------------------|
|
||||
| [Default Ripple][] | Disabled | Leave this setting **disabled.** (This is the default.) |
|
||||
| [Authorized Trust Lines][] | Enabled | Enable this setting on the hot address—and never approve any trust lines to the hot address—to prevent accidentally issuing tokens from the wrong address. (Optional, but recommended.) |
|
||||
| [Require Destination Tags][] | Enabled or Disabled | Enable if you process withdrawals of your token to outside systems. (For example, your token is a stablecoin.) |
|
||||
| Disallow XRP | Enabled or Disabled | Enable if this address isn't meant to process XRP payments. |
|
||||
| [Domain][] | (Your domain name) | Set to a domain you own so can [verify ownership of the accounts](../../../references/xrp-ledger-toml.md#account-verification). This can help reduce confusion or impersonation attempts. |
|
||||
|
||||
The following code sample shows how to send an [AccountSet transaction][] to enable the recommended hot address settings:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/js/issue-a-token.js" from="// Configure hot address" before="// Create trust line" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/py/issue-a-token.py" from="# Configure hot address" before="# Create trust line" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/java/IssueToken.java" from="// Configure hot address" before="// Create trust line" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Configure Hot Address" steps=$frontmatter.steps %}
|
||||
|
||||
<form>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="hot-default-ripple" disabled="disabled" title="Default Ripple must remain disabled on the hot address." />
|
||||
<label for="hot-default-ripple" class="form-check-label" title="Default Ripple must remain disabled on the hot address.">Default Ripple</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="hot-require-auth" disabled="disabled" checked="checked" title="The hot address should enable Authorized Trust Lines as a precaution." />
|
||||
<label for="hot-default-ripple" class="form-check-label" title="The hot address should enable Authorized Trust Lines as a precaution.">Authorized Trust Lines</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="hot-require-dest" />
|
||||
<label for="hot-require-dest" class="form-check-label">Require Destination Tags</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="hot-disallow-xrp" checked="checked" />
|
||||
<label for="hot-disallow-xrp" class="form-check-label">Disallow XRP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="hot-domain-text" class="col-form-label col-sm-3">Domain</label>
|
||||
<div class="input-group col">
|
||||
<div>
|
||||
<input type="text" class="form-control" value="example.com" pattern="([a-z0-9][a-z0-9\-]{0,62}[.])+[a-z]{2,6}" id="hot-domain-text" title="lower-case domain name of the account owner" />
|
||||
<small class="form-text">(text)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group col">
|
||||
<div class="col">
|
||||
<label class="form-control-plaintext" id="hot-domain-hex" for="hot-domain-text">6578616D706C652E636F6D</label>
|
||||
<small class="form-text">(hexadecimal)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button id="config-hot-address-button" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait (Hot Address Setup)">Configure hot address</button>
|
||||
|
||||
{% loading-icon message="Sending transaction..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
### 6. Wait for Validation
|
||||
|
||||
As before, wait for the previous transaction to be validated by consensus before continuing.
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" variables={label: "Wait (Hot Address Setup)"} /%}
|
||||
|
||||
|
||||
### 7. Create Trust Line from Hot to Cold Address
|
||||
|
||||
Before you can receive tokens, you need to create a [trust line](../../../concepts/tokens/fungible-tokens/index.md) to the token issuer. This trust line is specific to the [currency code](../../../references/protocol/data-types/currency-formats.md#currency-codes) of the token you want to issue, such as USD or FOO. You can choose any currency code you want; each issuer's tokens are treated as separate in the XRP Ledger protocol. However, users' balances of tokens with the same currency code can [ripple](../../../concepts/tokens/fungible-tokens/rippling.md) between different issuers if the users enable rippling settings.
|
||||
|
||||
The hot address needs a trust line like this before it can receive tokens from the issuer. Similarly, each user who wants to hold your token must also create a trust line[¹](#footnotes). Each trust line increases the [reserve requirement](../../../concepts/accounts/reserves.md) of the hot address, so you must hold enough spare XRP to pay for the increased requirement. Your reserve requirement goes back down if you remove the trust line.
|
||||
|
||||
**Tip:** A trust line has a "limit" on how much the recipient is willing to hold; others cannot send you more tokens than your specified limit. For community credit systems, you may want to configure limits per individual based on how much you trust that person. For other types and uses of tokens, it is normally OK to set the limit to a very large number.
|
||||
|
||||
To create a trust line, send a [TrustSet transaction][] from the **hot address** with the following fields:
|
||||
|
||||
| Field | Value |
|
||||
|:-----------------------|:----------------------------------------------------|
|
||||
| `TransactionType` | `"TrustSet"` |
|
||||
| `Account` | The hot address. (More generally, this is the account that wants to receive the token.) |
|
||||
| `LimitAmount` | An object specifying how much, of which token, from which issuer, you are willing to hold. |
|
||||
| `LimitAmount.currency` | The currency code of the token. |
|
||||
| `LimitAmount.issuer` | The cold address. |
|
||||
| `LimitAmount.value` | The maximum amount of the token you are willing to hold. |
|
||||
|
||||
The following code sample shows how to send a [TrustSet transaction][] from the hot address, trusting the issuing address for a limit of 1 billion FOO:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/js/issue-a-token.js" from="// Create trust line" before="// Send token" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/py/issue-a-token.py" from="# Create trust line" before="# Send token" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/java/IssueToken.java" from="// Create trust line" before="// Send token" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Make Trust Line" steps=$frontmatter.steps %}
|
||||
|
||||
<form>
|
||||
<p>Currency code:</p>
|
||||
<div class="container">
|
||||
<div class="input-group row">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text form-check bg-transparent">
|
||||
<input type="radio" id="use-std-code" name="currency-code-type" checked="checked" />
|
||||
</div>
|
||||
</div>
|
||||
<label for="use-std-code" class="input-group-text col-lg-3">Standard:</label>
|
||||
<input type="text" id="currency-code-std" pattern="[A-Za-z0-9?!@#$%*\(\)\{\}\|\x26\x3c\x3e]{3}" value="FOO" class="form-control col-lg-8" title="3 character code (letters, numbers, and some symbols)" />
|
||||
</div>
|
||||
<div class="input-group row mt-2">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text form-check bg-transparent">
|
||||
<input type="radio" id="use-hex-code" name="currency-code-type" />
|
||||
</div>
|
||||
</div>
|
||||
<label for="use-hex-code" class="input-group-text col-lg-3">Non-standard:</label>
|
||||
<input type="text" id="currency-code-hex" pattern="[0-9A-F]{40}" value="015841551A748AD2C1F76FF6ECB0CCCD00000000" title="40 hexadecimal characters" class="form-control col-lg-8" />
|
||||
</div>
|
||||
<div class="input-group row mt-4">
|
||||
<label for="trust-limit" class="input-group-text col-lg-3">Limit:</label>
|
||||
<input type="number" id="trust-limit" min="0" value="1000000000" title="Maximum amount the hot address can hold" class="form-control col-lg-9" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button id="create-trust-line-button" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait (TrustSet)">Create Trust Line</button>
|
||||
|
||||
{% loading-icon message="Sending transaction..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
**Note:** If you use [Authorized Trust Lines][], there is an extra step after this one: the cold address must approve the trust line from the hot address. For details of how to do this, see [Authorizing Trust Lines](../../../concepts/tokens/fungible-tokens/authorized-trust-lines.md#authorizing-trust-lines).
|
||||
|
||||
|
||||
### 8. Wait for Validation
|
||||
|
||||
As before, wait for the previous transaction to be validated by consensus before continuing.
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" variables={label: "Wait (TrustSet)"} /%}
|
||||
|
||||
|
||||
### 9. Send Token
|
||||
|
||||
Now you can create tokens by sending a [Payment transaction][] from the cold address to the hot address. This transaction should have the following attributes (dot notation indicates nested fields):
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| `TransactionType` | `"Payment"` |
|
||||
| `Account` | The cold address issuing the token. |
|
||||
| `Amount` | An [token amount](../../../references/protocol/data-types/basic-data-types.md#specifying-currency-amounts) specifying how much of which token to create. |
|
||||
| `Amount.currency` | The currency code of the token. |
|
||||
| `Amount.value` | Decimal amount of the token to issue, as a string. |
|
||||
| `Amount.issuer` | The cold address issuing the token. |
|
||||
| `Destination` | The hot address (or other account receiving the token) |
|
||||
| `Paths` | Omit this field when issuing tokens. |
|
||||
| `SendMax` | Omit this field when issuing tokens. |
|
||||
| `DestinationTag` | Any whole number from 0 to 2<sup>32</sup>-1. You must specify _something_ here if you enabled [Require Destination Tags][] on the hot address. |
|
||||
|
||||
You can use [auto-filled values](../../../references/protocol/transactions/common-fields.md#auto-fillable-fields) for all other required fields.
|
||||
|
||||
The following code sample shows how to send a [Payment transaction][] to issue 88 FOO from the cold address to the hot address:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/js/issue-a-token.js" from="// Send token" before="// Check balances" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/py/issue-a-token.py" from="# Send token" before="# Check balances" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/java/IssueToken.java" from="// Send token" before="// Check balances" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Send Token" steps=$frontmatter.steps %}
|
||||
|
||||
<form>
|
||||
<div class="form-inline mt-2">
|
||||
<div class="input-group">
|
||||
<label for="send-amount" class="input-group-text">Send amount:</label>
|
||||
<input type="number" id="send-amount" min="0" value="123.45" step="0.01" title="How much to send to the hot address" class="form-control" />
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text" id="send-currency-code">FOO</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline mt-2">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text bg-transparent">
|
||||
<input type="checkbox" id="use-dest-tag" class="mr-2" checked="checked" />
|
||||
</div>
|
||||
</div>
|
||||
<label for="dest-tag" class="input-group-text">DestinationTag:</label>
|
||||
<input type="number" id="dest-tag" value="0" min="0" max="4294967295" class="form-control" title="Any 32-bit integer" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button id="send-token-button" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait (Payment)">Send Token</button>
|
||||
|
||||
{% loading-icon message="Sending transaction..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 10. Wait for Validation
|
||||
|
||||
As before, wait for the previous transaction to be validated by consensus before continuing.
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" variables={label: "Wait (Payment)"} /%}
|
||||
|
||||
|
||||
### 11. Confirm Token Balances
|
||||
|
||||
You can check the balances of your token from the perspective of either the token issuer or the hot address. Tokens issued in the XRP Ledger always have balances that sum to 0: negative from the perspective of the issuer and positive from the perspective of the holder.
|
||||
|
||||
Use the [account_lines method][] to look up the balances from the perspective of the holder. This lists each trust line along with its limit, balance, and settings.
|
||||
|
||||
Use the [gateway_balances method][] to look up balances from the perspective of a token issuer. This provides a sum of all tokens issued by a given address.
|
||||
|
||||
**Tip:** Since the XRP Ledger is fully public, you can check the balances of any account at any time without needing any cryptographic keys.
|
||||
|
||||
The following code sample shows how to use both methods:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/js/issue-a-token.js" from="// Check balances" before="// End of" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/py/issue-a-token.py" from="# Check balances" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/issue-a-token/java/IssueToken.java" from="// Check balances" before="// Helper" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Confirm Balances" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="confirm-balances-button" class="btn btn-primary previous-steps-required">Confirm Balances</button>
|
||||
|
||||
{% loading-icon message="Checking..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### Next Steps
|
||||
|
||||
Now that you've created the token, you can explore how it fits into features of the XRP Ledger:
|
||||
|
||||
- Send tokens from the hot address to other users.
|
||||
- Trade it in the decentralized exchange.
|
||||
- Monitor for incoming payments of your token.
|
||||
- Create an [xrp-ledger.toml file](../../../references/xrp-ledger-toml.md) and set up domain verification for your token's issuer.
|
||||
- Learn about other [features of XRP Ledger tokens](../../../concepts/tokens/index.md).
|
||||
|
||||
|
||||
## Footnotes
|
||||
|
||||
¹ Users can hold your token without explicitly creating a trust line if they buy your token in the [decentralized exchange](../../../concepts/tokens/decentralized-exchange/index.md). Buying a token in the exchange [automatically creates the necessary trust lines](../../../concepts/tokens/decentralized-exchange/offers.md#offers-and-trust). This is only possible if someone is selling your token in the decentralized exchange.
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,266 @@
|
||||
---
|
||||
parent: use-tokens.html
|
||||
seo:
|
||||
description: Buy or sell fungible tokens for each other or for XRP in the decentralized exchange.
|
||||
embed_xrpl_js: true
|
||||
filters:
|
||||
- interactive_steps
|
||||
labels:
|
||||
- Decentralized Exchange
|
||||
- Tokens
|
||||
steps: ['Connect', 'Generate', 'Look Up Offers',' Send OfferCreate', 'Wait', 'Check Metadata', 'Check Balances and Offers']
|
||||
---
|
||||
# Trade in the Decentralized Exchange
|
||||
|
||||
This tutorial demonstrates how you can buy and sell tokens in the [decentralized exchange](../../../concepts/tokens/decentralized-exchange/index.md) (DEX).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You need a connection to the XRP Ledger network. As shown in this tutorial, you can use public servers for testing.
|
||||
- You should be familiar with the Getting Started instructions for your preferred client library. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js/). See [Get Started Using JavaScript](../../javascript/get-started.md) for setup steps.
|
||||
- **Python** with the [`xrpl-py` library](https://xrpl-py.readthedocs.io/). See [Get Started using Python](../../python/get-started.md) for setup steps.
|
||||
- You can also read along and use the interactive steps in your browser without any setup.
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script src='https://cdn.jsdelivr.net/npm/bignumber.js@9.0.2/bignumber.min.js'></script>
|
||||
<script type="application/javascript" src="/js/tutorials/trade-in-the-dex.js"></script>
|
||||
|
||||
## Example Code
|
||||
|
||||
Complete sample code for all of the steps of this tutorial is available under the [MIT license](https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE).
|
||||
|
||||
- See [Code Samples: Trade in the Decentralized Exchange](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/trade-in-the-decentralized-exchange/) in the source repository for this website.
|
||||
|
||||
|
||||
## Steps
|
||||
|
||||
This tutorial demonstrates how to buy a fungible token in the decentralized exchange by selling XRP. (Other types of trades are possible, but selling a token, for example, requires you to have it first.) The example token used in this tutorial is as follows:
|
||||
|
||||
| Currency Code | Issuer | Notes |
|
||||
|---|---|---|
|
||||
| TST | `rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd` | A test token pegged to XRP at a rate of approximately 10 XRP per 1 TST. The issuer has existing Offers on the XRP Ledger Testnet to buy and sell these tokens. |
|
||||
|
||||
|
||||
### 1. Connect to Network
|
||||
|
||||
You must be connected to the network to submit transactions to it. Additionally, some languages (including JavaScript) require a high-precision number library for performing calculations on currency amounts you may find in the ledger. The following code shows how to connect to a public XRP Ledger Testnet server a supported [client library](../../../references/client-libraries.md) with the appropriate dependencies.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/js/base-with-bignumber.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/get-started/py/base-async.py" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Note:** The JavaScript code samples in this tutorial use the [`async`/`await` pattern](https://javascript.info/async-await). Since `await` needs to be used from within an `async` function, the remaining code samples are written to continue inside the `main()` function started here. You can also use Promise methods `.then()` and `.catch()` instead of `async`/`await` if you prefer.
|
||||
|
||||
For this tutorial, click the following button to connect:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
### 2. Get Credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address, a secret key, and some XRP. For development purposes, you can get these on the [{{use_network}}](../../../concepts/networks-and-servers/parallel-networks.md) using the following interface:
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/generate-step.md" /%}
|
||||
|
||||
When you're building production-ready software, you should use an existing account, and manage your keys using a [secure signing configuration](../../../concepts/transactions/secure-signing.md). The following code shows how to create a `Wallet` instance to use your keys:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/js/trade-in-the-dex.js" from="// Get credentials" before="// Define the proposed trade" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/py/trade-in-the-dex.py" from="# Get credentials" before="# Define the proposed trade" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
### 7. Look Up Offers
|
||||
|
||||
Before you buy or sell a token, you usually want to look up what others are buying and selling for, to get a sense of how others value it. In the XRP Ledger, you can look up existing offers for any currency pair using the [book_offers method][].
|
||||
|
||||
**Tip:** Technically, this step is not a requirement for placing an Offer, but it is a good practice to confirm the current situation before trading anything with real value.
|
||||
|
||||
The following code shows how to look up existing Offers and compare them to a proposed Offer to estimate how it would execute:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/js/trade-in-the-dex.js" from="// Define the proposed trade" before="// Send OfferCreate" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/py/trade-in-the-dex.py" from="# Define the proposed trade" before="# Send OfferCreate" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Note:** Other users of the XRP Ledger can also make trades at any time, so this is only an estimate of what would happen if nothing else changes. The outcome of a transaction is not guaranteed until it is [final](../../../concepts/transactions/finality-of-results/index.md).
|
||||
|
||||
The following block demonstrates these calculations in action:
|
||||
|
||||
{% interactive-block label="Look Up Offers" steps=$frontmatter.steps %}
|
||||
|
||||
<form>
|
||||
<h5>TakerPays</h5>
|
||||
<div class="form-group row">
|
||||
<label for="taker-pays-currency-1" class="col-form-label col-sm-3">currency</label>
|
||||
<div class="input-group col">
|
||||
<input type="text" class="form-control" id="taker-pays-currency-1" value="TST" disabled="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="taker-pays-issuer-1" class="col-form-label col-sm-3">issuer</label>
|
||||
<div class="input-group col">
|
||||
<input type="text" class="form-control" id="taker-pays-issuer-1" value="rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" disabled="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="taker-pays-amount-1" class="col-form-label col-sm-3">value</label>
|
||||
<div class="input-group col">
|
||||
<input type="number" class="form-control" id="taker-pays-amount-1" value="10" step="0.000001" min="0" max="1000" />
|
||||
</div>
|
||||
</div>
|
||||
<h5>TakerGets</h5>
|
||||
<div class="form-group row">
|
||||
<label for="taker-gets-amount-1" class="col-form-label col-sm-3">XRP:</label>
|
||||
<div class="input-group col">
|
||||
<input type="number" class="form-control" value="115" id="taker-gets-amount-1"
|
||||
aria-label="Amount of XRP, as a decimal" aria-describedby="xrp-amount-label"
|
||||
min=".000001" max="100000000000" step="any" />
|
||||
</div>
|
||||
</div>
|
||||
<h5>Exchange Rate</h5>
|
||||
<div class="form-group row">
|
||||
<label for="exchange-rate-1" class="col-form-label col-sm-3">XRP cost per 1 TST:</label>
|
||||
<div class="input-group col">
|
||||
<input type="number" class="form-control" value="11.5" id="exchange-rate-1" step="any" disabled="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button id="look-up-offers" class="btn btn-primary previous-steps-required">Look Up Offers</button>
|
||||
|
||||
{% loading-icon message="Querying..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
### 3. Send OfferCreate Transaction
|
||||
|
||||
To actually make a trade, send an [OfferCreate transaction][]. In this case, you want to buy TST using XRP, so you should set the parameters as follows:
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `TakerPays` | [Token Amount object][Currency Amount] | How much of what currency you want to buy, in total. For this tutorial, buy some amount of **TST** issued by `rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd`. |
|
||||
| `TakerGets` | [XRP, in drops][] | How much of what currency you are offering to pay in total. For this tutorial, you should specify about 11.5 XRP per TST or slightly more. |
|
||||
|
||||
The following code shows how to prepare, sign, and submit the transaction:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/js/trade-in-the-dex.js" from="// Send OfferCreate" before="// Check metadata" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/py/trade-in-the-dex.py" from="# Send OfferCreate" before="# Check metadata" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
You can use this interface to send the transaction specified by the amounts in the previous step:
|
||||
|
||||
{% interactive-block label="Send OfferCreate" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="send-offercreate" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait">Send OfferCreate</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
### 4. Wait for Validation
|
||||
|
||||
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. If the XRP Ledger is busy or poor network connectivity delays a transaction from being relayed throughout the network, a transaction may take longer to be confirmed. (For information on how to set an expiration for transactions, see [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).)
|
||||
|
||||
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" /%}
|
||||
|
||||
|
||||
### 5. Check Metadata
|
||||
|
||||
You can use the validated transaction's [metadata](../../../references/protocol/transactions/metadata.md) to determine exactly what it did. (Don't use metadata from tentative transaction results, because it may be different from the [final result](../../../concepts/transactions/finality-of-results/index.md), especially when using the decentralized exchange.) In case of an OfferCreate transaction, likely results include:
|
||||
|
||||
- Some or all of the Offer may have been filled by matching with existing Offers in the ledger.
|
||||
- The unmatched remainder, if any, has been placed into the ledger to await new matching Offers. <!-- STYLE_OVERRIDE: remainder -->
|
||||
- Other bookkeeping may have occurred, such as removing expired or unfunded Offers that would have matched.
|
||||
|
||||
The following code demonstrates how to check the metadata of the transaction:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/js/trade-in-the-dex.js" from="// Check metadata" before="// Check balances" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/py/trade-in-the-dex.py" from="# Check metadata" before="# Check balances" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
You can use this interface to test it out:
|
||||
|
||||
{% interactive-block label="Check Metadata" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="check-metadata" class="btn btn-primary previous-steps-required">Check Metadata</button>
|
||||
|
||||
{% loading-icon message="Checking..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 6. Check Balances and Offers
|
||||
|
||||
This is also a good time to look up the balances and outstanding Offers owned by your account as of the latest validated ledger. This shows any changes caused by your transaction as well as any others that executed in the same ledger version.
|
||||
|
||||
The following code demonstrates how to look up balances using the [account_lines method][] and look up Offers using the [account_offers method][].
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/js/trade-in-the-dex.js" from="// Check balances" before="client.disconnect()" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/trade-in-the-decentralized-exchange/py/trade-in-the-dex.py" from="# Check balances" before="# End main()" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
You can use this interface to test it out:
|
||||
|
||||
{% interactive-block label="Check Balances and Offers" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="check-balances-and-offers" class="btn btn-primary previous-steps-required">Check Balances and Offers</button>
|
||||
|
||||
{% loading-icon message="Checking..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -12,13 +12,13 @@ _(Requires the [XChainBridge amendment][] {% not-enabled /%})_
|
||||
|
||||
Setting up an IOU-IOU bridge enables you to move tokens between chains.
|
||||
|
||||
**Note**: The code samples on this page illustrate how to bridge a hypotethical "TST" token from *Devnet* to *Sidechain-Devnet*, using a supported [client library](../../../references/client-libraries.md) to query and submit transactions.
|
||||
**Note**: The code samples on this page illustrate how to bridge a hypotethical "TST" token from *Devnet* to *Sidechain-Devnet*, using a supported [client library](/docs/references/client-libraries.md) to query and submit transactions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An XRP-XRP bridge must be set up between the locking and issuing chain.
|
||||
- Ensure the witnesses' transaction submission accounts are funded on the locking and issuing chains.
|
||||
- Set up an issuer on the issuing chain to mint and burn a wrapped version of the token you want to bridge. See: [Issue a Fungible Token](../../use-tokens/issue-a-fungible-token.md)
|
||||
- Set up an issuer on the issuing chain to mint and burn a wrapped version of the token you want to bridge. See: [Issue a Fungible Token](../use-tokens/issue-a-fungible-token.md)
|
||||
|
||||
## Steps
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ _(Requires the [XChainBridge amendment][] {% not-enabled /%})_
|
||||
|
||||
Setting up an XRP-XRP bridge enables you to move XRP between chains. The set up requires using the genesis account on the issuing chain as a door account to submit attestations and create transaction submission accounts for witnesses.
|
||||
|
||||
**Note**: The code samples on this page illustrate how a bridge was set up between *Devnet* and *Sidechain-Devnet*, using a supported [client library](../../../references/client-libraries.md) to query and submit transactions. This bridge is already created, so the process can't be reproduced on these networks.
|
||||
**Note**: The code samples on this page illustrate how a bridge was set up between *Devnet* and *Sidechain-Devnet*, using a supported [client library](/docs/references/client-libraries.md) to query and submit transactions. This bridge is already created, so the process can't be reproduced on these networks.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Reference in New Issue
Block a user