From 910a5999c9dda17abbc9ec82feb2350bfa30e87b Mon Sep 17 00:00:00 2001 From: Mukul Jangid <49061120+mukulljangid@users.noreply.github.com> Date: Wed, 20 Oct 2021 12:29:48 -0400 Subject: [PATCH] Docs: Snippets (#1729) * refactor: adds snippets for sendEscrow, claimPayChannel, reliableSubmission, getTransaction, partialPayment, getPaths --- .eslintrc.js | 8 +- package.json | 4 +- snippets/src/claimPayChannel.ts | 74 +++++ snippets/src/decoder.ts | 17 - snippets/src/getTransaction.ts | 59 ++-- snippets/src/parseAccountFlags.ts | 25 -- snippets/src/partialPayment.ts | 91 ++++++ snippets/src/paths.ts | 100 +++--- snippets/src/reliableTransactionSubmission.ts | 294 +++++------------- snippets/src/sendEscrow.ts | 77 +++++ snippets/src/setRegularKey.ts | 54 ++++ src/models/ledger/Ledger.ts | 3 +- src/models/methods/accountTx.ts | 3 +- src/models/methods/transactionEntry.ts | 3 +- src/models/methods/tx.ts | 3 +- src/models/transactions/index.ts | 1 + src/utils/hashes/hashLedger.ts | 3 +- 17 files changed, 461 insertions(+), 358 deletions(-) create mode 100644 snippets/src/claimPayChannel.ts delete mode 100644 snippets/src/decoder.ts delete mode 100644 snippets/src/parseAccountFlags.ts create mode 100644 snippets/src/partialPayment.ts create mode 100644 snippets/src/sendEscrow.ts create mode 100644 snippets/src/setRegularKey.ts diff --git a/.eslintrc.js b/.eslintrc.js index f6088073..54a90a3c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -62,12 +62,14 @@ module.exports = { }, }, { - // TODO: remove when snippets are written files: ['snippets/src/*.ts'], rules: { - 'max-len': 'off', - 'import/unambiguous': 'off', 'import/no-unused-modules': 'off', + // Each file has a particular flow. + 'max-lines-per-function': 'off', + 'max-statements': 'off', + // Snippets have logs on console to better understand the working. + 'no-console': 'off', }, }, { diff --git a/package.json b/package.json index e781cc7e..fdd8e4ec 100644 --- a/package.json +++ b/package.json @@ -97,8 +97,8 @@ "lint": "eslint . --ext .ts --max-warnings 0", "perf": "./scripts/perf_test.sh", "compile:snippets": "tsc -p snippets/tsconfig.json", - "start:snippet": "npm run compile:snippets && node ./snippets/dist/start.js", - "inspect:snippet": "npm run compile:snippets && node inspect ./snippets/dist/start.js" + "start:snippet": "npm run compile:snippets && node", + "inspect:snippet": "npm run compile:snippets && node inspect" }, "prettier": "@xrplf/prettier-config", "repository": { diff --git a/snippets/src/claimPayChannel.ts b/snippets/src/claimPayChannel.ts new file mode 100644 index 00000000..a8213425 --- /dev/null +++ b/snippets/src/claimPayChannel.ts @@ -0,0 +1,74 @@ +import { + AccountObjectsRequest, + Client, + PaymentChannelCreate, + PaymentChannelClaim, + hashes, +} from '../../dist/npm' + +const client = new Client('wss://s.altnet.rippletest.net:51233') + +void claimPayChannel() + +// The snippet walks us through creating and claiming a Payment Channel. +async function claimPayChannel(): Promise { + await client.connect() + + // creating wallets as prerequisite + const { wallet: wallet1 } = await client.fundWallet() + const { wallet: wallet2 } = await client.fundWallet() + + console.log('Balances of wallets before Payment Channel is claimed:') + console.log(await client.getXrpBalance(wallet1.classicAddress)) + console.log(await client.getXrpBalance(wallet2.classicAddress)) + + // create a Payment Channel and submit and wait for tx to be validated + const paymentChannelCreate: PaymentChannelCreate = { + TransactionType: 'PaymentChannelCreate', + Account: wallet1.classicAddress, + Amount: '100', + Destination: wallet2.classicAddress, + SettleDelay: 86400, + PublicKey: wallet1.publicKey, + } + + const paymentChannelResponse = await client.submitAndWait( + paymentChannelCreate, + { wallet: wallet1 }, + ) + console.log(paymentChannelResponse) + + // check that the object was actually created + const accountObjectsRequest: AccountObjectsRequest = { + command: 'account_objects', + account: wallet1.classicAddress, + } + + const accountObjects = (await client.request(accountObjectsRequest)).result + .account_objects + + console.log(accountObjects) + + // destination claims the Payment Channel and we see the balances to verify. + const paymentChannelClaim: PaymentChannelClaim = { + Account: wallet2.classicAddress, + TransactionType: 'PaymentChannelClaim', + Channel: hashes.hashPaymentChannel( + wallet1.classicAddress, + wallet2.classicAddress, + paymentChannelResponse.result.Sequence ?? 0, + ), + Amount: '100', + } + + const channelClaimResponse = await client.submit(paymentChannelClaim, { + wallet: wallet1, + }) + console.log(channelClaimResponse) + + console.log('Balances of wallets after Payment Channel is claimed:') + console.log(await client.getXrpBalance(wallet1.classicAddress)) + console.log(await client.getXrpBalance(wallet2.classicAddress)) + + await client.disconnect() +} diff --git a/snippets/src/decoder.ts b/snippets/src/decoder.ts deleted file mode 100644 index f749eaa8..00000000 --- a/snippets/src/decoder.ts +++ /dev/null @@ -1,17 +0,0 @@ -// import * as codec from 'ripple-binary-codec' - -/* - * const original = codec.decode( - * '12000022800200002400000001201B00EF81E661EC6386F26FC0FFFF0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461968400000000000000C6940000000000000646AD3504529A0465E2E0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D1664619732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402200A693FB5CA6B21250EBDFD8CFF526EE0DF7C9E4E31EB0660692E75E6A93BF5F802203CC39463DDA21386898CA31E18AD1A6828647D65741DD637BAD71BC83E29DB9481145E7B112523F68D2F5E879DB4EAC51C6698A693048314CA6EDC7A28252DAEA6F2045B24F4D7C333E146170112300000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461900' - * ) - */ - -/* - * const test = codec.decode( - * '12000022800200002400000017201B008694F261EC6386F26FC0FFFF0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461968400000000000000C6940000000000000646AD3504529A0465E2E0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D1664619732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100D8B57E8E06EAE27B1343AF8CAD3F501E18260CCF8BCED08066074106F0F191A3022058FEA6CE9E7FA69D1244C3A70F18983CC2DAF0B10CBB86A6677CF2A5D2B8A68081145E7B112523F68D2F5E879DB4EAC51C6698A693048314CA6EDC7A28252DAEA6F2045B24F4D7C333E146170112300000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461900' - * ) - */ - -// console.log('original:', JSON.stringify(original)) - -// console.log('test:', JSON.stringify(test)) diff --git a/snippets/src/getTransaction.ts b/snippets/src/getTransaction.ts index 80f76533..8f041c98 100644 --- a/snippets/src/getTransaction.ts +++ b/snippets/src/getTransaction.ts @@ -1,26 +1,39 @@ -/* - * import {Client} from '../../dist/npm' - * import {TransactionMetadata} from 'xrpl-local/models/common/transaction' - */ +import { Client, LedgerResponse, TxResponse } from '../../dist/npm' -// const client = new Client('wss://s.altnet.rippletest.net:51233') +const client = new Client('wss://s.altnet.rippletest.net:51233') -// getTransaction() +async function getTransaction(): Promise { + await client.connect() + const ledger: LedgerResponse = await client.request({ + command: 'ledger', + transactions: true, + ledger_index: 'validated', + }) + console.log(ledger) -/* - * async function getTransaction() { - * await client.connect() - * const ledger = await client.request({command: 'ledger', transactions: true}) - * console.log(ledger) - * const tx = await client.request({ - * command: 'tx', - * transaction: ledger.result.ledger.transactions[0] as string - * }) - * console.log(tx) - * console.log( - * 'deliveredAmount:', - * (tx.result.meta as TransactionMetadata).DeliveredAmount - * ) - * process.exit(0) - * } - */ + const transactions = ledger.result.ledger.transactions + if (transactions) { + const tx: TxResponse = await client.request({ + command: 'tx', + transaction: transactions[0], + }) + console.log(tx) + + // The meta field would be a string(hex) when the `binary` parameter is `true` for the `tx` request. + if (tx.result.meta == null) { + throw new Error('meta not included in the response') + } + /* + * delivered_amount is the amount actually received by the destination account. + * Use this field to determine how much was delivered, regardless of whether the transaction is a partial payment. + * https://xrpl.org/transaction-metadata.html#delivered_amount + */ + if (typeof tx.result.meta !== 'string') { + console.log('delivered_amount:', tx.result.meta.delivered_amount) + } + } + + await client.disconnect() +} + +void getTransaction() diff --git a/snippets/src/parseAccountFlags.ts b/snippets/src/parseAccountFlags.ts deleted file mode 100644 index 555441f4..00000000 --- a/snippets/src/parseAccountFlags.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * import {Client} from '../../dist/npm' - * import {AccountFlags} from '../../dist/npm/common/constants' - */ - -// const client = new Client('wss://s.altnet.rippletest.net:51233') - -// parseAccountFlags() - -/* - * async function parseAccountFlags() { - * await client.connect() - * const account_info = await client.request({ - * command: 'account_info', - * account: 'rKsdkGhyZH6b2Zzd5hNnEqSv2wpznn4n6N' - * }) - * const flags = account_info.result.account_data.Flags - * for (const flagName in AccountFlags) { - * if (flags & AccountFlags[flagName]) { - * console.log(`${flagName} enabled`) - * } - * } - * process.exit(0) - * } - */ diff --git a/snippets/src/partialPayment.ts b/snippets/src/partialPayment.ts new file mode 100644 index 00000000..a8b77265 --- /dev/null +++ b/snippets/src/partialPayment.ts @@ -0,0 +1,91 @@ +import { Client, Payment, PaymentFlags, TrustSet } from '../../dist/npm' + +const client = new Client('wss://s.altnet.rippletest.net:51233') + +// This snippet walks us through partial payment. +async function partialPayment(): Promise { + await client.connect() + + // creating wallets as prerequisite + const { wallet: wallet1 } = await client.fundWallet() + const { wallet: wallet2 } = await client.fundWallet() + + // create a trustline to issue an IOU `FOO` and set limit on it. + const trust_set_tx: TrustSet = { + TransactionType: 'TrustSet', + Account: wallet2.classicAddress, + LimitAmount: { + currency: 'FOO', + issuer: wallet1.classicAddress, + // Value for the new IOU - 10000000000 - is arbitarily chosen. + value: '10000000000', + }, + } + + await client.submitAndWait(trust_set_tx, { + wallet: wallet2, + }) + + console.log('Balances after trustline is created') + console.log(await client.getBalances(wallet1.classicAddress)) + console.log(await client.getBalances(wallet2.classicAddress)) + + // Initially, the issuer(wallet1) sends an amount to the other account(wallet2) + const issue_quantity = '3840' + const payment: Payment = { + TransactionType: 'Payment', + Account: wallet1.classicAddress, + Amount: { + currency: 'FOO', + value: issue_quantity, + issuer: wallet1.classicAddress, + }, + Destination: wallet2.classicAddress, + } + + // submit payment + const initialPayment = await client.submitAndWait(payment, { + wallet: wallet1, + }) + console.log(initialPayment) + + console.log('Balances after issuer(wallet1) sends IOU("FOO") to wallet2') + console.log(await client.getBalances(wallet1.classicAddress)) + console.log(await client.getBalances(wallet2.classicAddress)) + + /* + * Send money less than the amount specified on 2 conditions: + * 1. Sender has less money than the aamount specified in the payment Tx. + * 2. Sender has the tfPartialPayment flag activated. + * + * Other ways to specify flags are by using Hex code and decimal code. + * eg. For partial payment(tfPartialPayment) + * decimal ->131072, hex -> 0x00020000 + */ + const partialPaymentTx: Payment = { + TransactionType: 'Payment', + Account: wallet2.classicAddress, + Amount: { + currency: 'FOO', + value: '4000', + issuer: wallet1.classicAddress, + }, + Destination: wallet1.classicAddress, + Flags: PaymentFlags.tfPartialPayment, + } + + // submit payment + const submitResponse = await client.submitAndWait(partialPaymentTx, { + wallet: wallet2, + }) + console.log(submitResponse) + + console.log( + "Balances after Partial Payment, when wallet2 tried to send 4000 FOO's", + ) + console.log(await client.getBalances(wallet1.classicAddress)) + console.log(await client.getBalances(wallet2.classicAddress)) + + await client.disconnect() +} +void partialPayment() diff --git a/snippets/src/paths.ts b/snippets/src/paths.ts index 90ce2c4a..dff365b5 100644 --- a/snippets/src/paths.ts +++ b/snippets/src/paths.ts @@ -1,63 +1,49 @@ -// import {Client} from '../../dist/npm' +import { Client, Payment, RipplePathFindResponse } from '../../dist/npm' -/* - * const client = new Client( - * // 'wss://s.altnet.rippletest.net:51233' - * // 'ws://35.158.96.209:51233' - * 'ws://34.210.87.206:51233' - * ) - */ +const client = new Client('wss://s.altnet.rippletest.net:51233') -// sign() +async function createTxWithPaths(): Promise { + await client.connect() -/* - * async function sign() { - * await client.connect() - * const pathfind: any = { - * source: { - * address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - * amount: { - * currency: 'drops', - * value: '100' - * } - * }, - * destination: { - * address: 'rKT4JX4cCof6LcDYRz8o3rGRu7qxzZ2Zwj', - * amount: { - * currency: 'USD', - * counterparty: 'rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc' - * } - * } - * } - */ + const { wallet } = await client.fundWallet() + const destination_account = 'rKT4JX4cCof6LcDYRz8o3rGRu7qxzZ2Zwj' + const destination_amount = { + value: '0.001', + currency: 'USD', + issuer: 'rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc', + } -/* - * await client - * .getPaths(pathfind) - * .then(async (data) => { - * console.log('paths:', JSON.stringify(data)) - * const fakeSecret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV' - */ + const request = { + command: 'ripple_path_find', + source_account: wallet.classicAddress, + source_currencies: [ + { + currency: 'XRP', + }, + ], + destination_account, + destination_amount, + } -/* - * pathfind.paths = data[0].paths - * pathfind.destination = data[0].destination - * await client - * .preparePayment('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', pathfind) - * .then((ret) => { - * const signed = client.sign(ret.txJSON, fakeSecret) - * console.log('signed:', signed) - * }) - * .catch((err) => { - * console.log('ERR 1:', JSON.stringify(err)) - * }) - * }) - * .catch((err) => { - * console.log('ERR 2:', err) - * }) - */ + const resp: RipplePathFindResponse = await client.request(request) + console.log(resp) -/* - * client.disconnect() - * } - */ + const paths = resp.result.alternatives[0].paths_computed + console.log(paths) + + const tx: Payment = { + TransactionType: 'Payment', + Account: wallet.classicAddress, + Amount: destination_amount, + Destination: destination_account, + Paths: paths, + } + + await client.autofill(tx) + const signed = wallet.sign(tx) + console.log('signed:', signed) + + await client.disconnect() +} + +void createTxWithPaths() diff --git a/snippets/src/reliableTransactionSubmission.ts b/snippets/src/reliableTransactionSubmission.ts index 8c7290c9..adbcf945 100644 --- a/snippets/src/reliableTransactionSubmission.ts +++ b/snippets/src/reliableTransactionSubmission.ts @@ -1,231 +1,83 @@ -// import https = require('https') +import { Client, Payment } from '../../dist/npm' -// import {Client, AccountInfoResponse, LedgerClosedEvent} from '../../dist/npm' - -// /** -// * When implementing Reliable Transaction Submission, there are many potential solutions, each with different trade-offs. The main decision points are: -// * 1) Transaction preparation: -// * - How do we decide which account sequence and LastLedgerSequence numbers to use? -// * (To prevent unintentional duplicate transactions, an {account, account_sequence} pair can be used as a transaction's idempotency key) -// * - How do we decide how much to pay for the transaction fee? (If our transactions have been failing due to low fee, we should consider increasing this value) -// * 2) Transaction status retrieval. Options include: -// * - Poll for transaction status: -// * - On a regular interval (e.g. Every 3-5 seconds), or -// * - When a new validated ledger is detected -// * + (To accommodate an edge case in transaction retrieval, check the sending account's Sequence number to confirm that it has the expected value; -// * alternatively, wait until a few additional ledgers have been validated before deciding that a transaction has definitively not been included in a validated ledger) -// * - Listen for transaction status: scan all validated transactions to see if our transactions are among them -// * 3) What do we do when a transaction fails? It is possible to implement retry logic, but caution is advised. Note that there are a few ways for a transaction to fail: -// * A) `tec`: The transaction was included in a ledger but only claimed the transaction fee -// * B) `tesSUCCESS` but unexpected result: The transaction was successful but did not have the expected result. This generally does not occur for XRP-to-XRP payments -// * C) The transaction was not, and never will be, included in a validated ledger [3C]. -// * -// * References: -// * - https://xrpl.org/reliable-transaction-submission.html -// * - https://xrpl.org/send-xrp.html -// * - https://xrpl.org/look-up-transaction-results.html -// * - https://xrpl.org/get-started-with-rippleapi-for-javascript.html -// * - https://xrpl.org/monitor-incoming-payments-with-websocket.html. -// * -// * For the implementation in this example, we have made the following decisions: -// * 1) The script will choose the account sequence and LastLedgerSequence numbers automatically. We allow xrpl.js to choose the fee. -// * Payments are defined upfront, and idempotency is not needed. If the script is run a second time, duplicate payments will result. -// * 2) We will listen for notification that a new validated ledger has been found, and poll for transaction status at that time. -// * Futhermore, as a precaution, we will wait until the server is 3 ledgers past the transaction's LastLedgerSequence -// * (with the transaction nowhere to be seen) before deciding that it has definitively failed per [3C] -// * 3) Transactions will not be automatically retried. Transactions are limited to XRP-to-XRP payments and cannot "succeed" in an unexpected way. -// */ -// reliableTransactionSubmissionExample() - -// async function reliableTransactionSubmissionExample() { -// /** -// * Array of payments to execute. -// * -// * For brevity, these are XRP-to-XRP payments, taking a source, destination, and an amount in drops. -// * -// * The script will attempt to make all of these payments as quickly as possible, and report the final status of each. Transactions that fail are NOT retried. -// */ -// const payments = [] - -/* - * const sourceAccount = (await generateTestnetAccount()).account - * console.log( - * `Generated new Testnet account: ${sourceAccount.classicAddress}/${sourceAccount.secret}` - * ) - * // Send amounts from 1 drop to 10 drops - * for (let i = 1; i <= 10; i++) { - * payments.push({ - * source: sourceAccount, - * destination: 'rhsoCozhUxwcyQgzFi1FVRoMVQgk7cZd4L', // Random Testnet destination - * amount_drops: i.toString() - * }) - * } - * const results = await performPayments(payments) - * console.log(JSON.stringify(results, null, 2)) - * process.exit(0) - * } +/** + * When implementing Reliable Transaction Submission, there are many potential solutions, each with different trade-offs. + * The main decision points are: + * 1) Transaction preparation: + * - The autofill function as a part of the submitAndWait should be able to correctly populate + * values for the fields Sequence, LastLedgerSequence and Fee. + * 2) Transaction status retrieval. Options include: + * - Poll for transaction status: + * - On a regular interval (e.g. Every 3-5 seconds), or + * - When a new validated ledger is detected + * + (To accommodate an edge case in transaction retrieval, + * check the sending account's Sequence number to confirm that it has the expected value; + * alternatively, wait until a few additional ledgers have been validated before deciding that a + * transaction has definitively not been included in a validated ledger) + * - Listen for transaction status: scan all validated transactions to see if our transactions are among them + * 3) What do we do when a transaction fails? It is possible to implement retry logic, but caution is advised. + * Note that there are a few ways for a transaction to fail: + * A) `tec`: The transaction was included in a ledger but only claimed the transaction fee + * B) `tesSUCCESS` but unexpected result: The transaction was successful but did not have the expected result. + * This generally does not occur for XRP-to-XRP payments + * C) The transaction was not, and never will be, included in a validated ledger [3C]. + * + * References: + * - https://xrpl.org/reliable-transaction-submission.html + * - https://xrpl.org/send-xrp.html + * - https://xrpl.org/look-up-transaction-results.html + * - https://xrpl.org/monitor-incoming-payments-with-websocket.html. + * + * For the implementation in this example, we have made the following decisions: + * 1) We allow the autofill function as a part of submitAndWait to fill up the account sequence, + * LastLedgerSequence and Fee. Payments are defined upfront, and idempotency is not needed. + * If the script is run a second time, duplicate payments will result. + * 2) We will rely on the xrpl.js submitAndWait function to get us the transaction submission result after the wait time. + * 3) Transactions will not be automatically retried. Transactions are limited to XRP-to-XRP payments + * and cannot "succeed" in an unexpected way. */ -/* - * async function performPayments(payments) { - * const finalResults = [] - * const txFinalizedPromises = [] - * const client = new Client('wss://s.altnet.rippletest.net:51233') - * await client.connect() - */ +const client = new Client('wss://s.altnet.rippletest.net:51233') -/* - * for (let i = 0; i < payments.length; i++) { - * const payment = payments[i] - * const account_info: AccountInfoResponse = await client.request({ - * command: 'account_info', - * account: payment.source.classicAddress, - * ledger_index: 'current' - * }) - * const sequence = account_info.result.account_data.Sequence - * const preparedPayment = await client.preparePayment( - * payment.source.classicAddress, - * { - * source: { - * address: payment.source.classicAddress, - * amount: { - * value: payment.amount_drops, - * currency: 'drops' - * } - * }, - * destination: { - * address: payment.destination, - * minAmount: { - * value: payment.amount_drops, - * currency: 'drops' - * } - * } - * }, - * { - * sequence - * } - * ) - * const signed = client.sign(preparedPayment.txJSON, payment.source.secret) - * finalResults.push({ - * id: signed.id - * }) - * const response = await client.request({ - * command: 'submit', - * tx_blob: signed.signedTransaction - * }) - */ +void sendReliableTx() -/* - * // Most of the time we'll get 'tesSUCCESS' or (after many submissions) 'terQUEUED' - * console.log(`tx ${i} - tentative: ${response.result.engine_result}`) - */ +async function sendReliableTx(): Promise { + await client.connect() -/* - * const txFinalizedPromise = new Promise((resolve) => { - * const ledgerClosedCallback = async (event: LedgerClosedEvent) => { - * let status - * try { - * status = await client.request({command: 'tx', transaction: signed.id}) - * } catch (e) { - * // Typical error when the tx hasn't been validated yet: - * if (e.name !== 'MissingLedgerHistoryError') { - * console.log(e) - * } - */ + // creating wallets as prerequisite + const { wallet: wallet1 } = await client.fundWallet() + const { wallet: wallet2 } = await client.fundWallet() -/* - * if ( - * event.ledger_index > - * preparedPayment.instructions.maxLedgerVersion + 3 - * ) { - * // Assumptions: - * // - We are still connected to the same rippled server - * // - No ledger gaps occurred - * // - All ledgers between the time we submitted the tx and now have been checked for the tx - * status = { - * finalResult: - * 'Transaction was not, and never will be, included in a validated ledger' - * } - * } else { - * // Check again later: - * client.connection.once('ledgerClosed', ledgerClosedCallback) - * return - * } - * } - */ + console.log('Balances of wallets before Payment tx') + console.log( + await client.getXrpBalance(wallet1.classicAddress), + await client.getXrpBalance(wallet2.classicAddress), + ) -/* - * for (let j = 0; j < finalResults.length; j++) { - * if (finalResults[j].id === signed.id) { - * finalResults[j].result = status.address - * ? { - * source: status.address, - * destination: status.specification.destination.address, - * deliveredAmount: status.outcome.deliveredAmount, - * result: status.outcome.result, - * timestamp: status.outcome.timestamp, - * ledgerVersion: status.outcome.ledgerVersion - * } - * : status - * process.stdout.write('.') - * return resolve() - * } - * } - * } - * client.connection.once('ledgerClosed', ledgerClosedCallback) - * }) - * txFinalizedPromises.push(txFinalizedPromise) - * } - * await Promise.all(txFinalizedPromises) - * return finalResults - * } - */ + // create a Payment tx and submit and wait for tx to be validated + const payment: Payment = { + TransactionType: 'Payment', + Account: wallet1.classicAddress, + Amount: '1000', + Destination: wallet2.classicAddress, + } -// /** -// * Generate a new Testnet account by requesting one from the faucet. -// */ -// async function generateTestnetAccount(): Promise<{ -// account: { -// xAddress: string -// classicAddress -// string -// secret: string -// } -// balance: number -// }> { -// const options = { -// hostname: 'faucet.altnet.rippletest.net', -// port: 443, -// path: '/accounts', -// method: 'POST' -// } -// return new Promise((resolve, reject) => { -// const request = https.request(options, (response) => { -// const chunks = [] -// response.on('data', (d) => { -// chunks.push(d) -// }) -// response.on('end', () => { -// const body = Buffer.concat(chunks).toString() + const paymentResponse = await client.submitAndWait(payment, { + wallet: wallet1, + }) + console.log('\nTransaction was submitted.\n') + const txResponse = await client.request({ + command: 'tx', + transaction: paymentResponse.result.hash, + }) + // With the following reponse we are able to see that the tx was indeed validated. + console.log('Validated:', txResponse.result.validated) -/* - * // "application/json; charset=utf-8" - * if (response.headers['content-type'].startsWith('application/json')) { - * resolve(JSON.parse(body)) - * } else { - * reject({ - * statusCode: response.statusCode, - * contentType: response.headers['content-type'], - * body - * }) - * } - * }) - * }) - * request.on('error', (error) => { - * console.error(error) - * reject(error) - * }) - * request.end() - * }) - * } - */ + console.log('Balances of wallets after Payment tx:') + console.log( + await client.getXrpBalance(wallet1.classicAddress), + await client.getXrpBalance(wallet2.classicAddress), + ) + + await client.disconnect() +} diff --git a/snippets/src/sendEscrow.ts b/snippets/src/sendEscrow.ts new file mode 100644 index 00000000..3d06ee36 --- /dev/null +++ b/snippets/src/sendEscrow.ts @@ -0,0 +1,77 @@ +import { + AccountObjectsRequest, + Client, + EscrowCreate, + EscrowFinish, + isoTimeToRippleTime, +} from '../../dist/npm' + +const client = new Client('wss://s.altnet.rippletest.net:51233') + +void sendEscrow() + +// The snippet walks us through creating and finishing escrows. +async function sendEscrow(): Promise { + await client.connect() + + // creating wallets as prerequisite + const { wallet: wallet1 } = await client.fundWallet() + const { wallet: wallet2 } = await client.fundWallet() + + console.log('Balances of wallets before Escrow tx was created:') + console.log( + await client.getXrpBalance(wallet1.classicAddress), + await client.getXrpBalance(wallet2.classicAddress), + ) + + // finish 2 seconds after the escrow is actually created and tx is validated. + const finishAfter = isoTimeToRippleTime(Date()) + 2 + + // creating an Escrow using `EscrowCreate` and submit and wait for tx to be validated. + const createTx: EscrowCreate = { + TransactionType: 'EscrowCreate', + Account: wallet1.address, + Destination: wallet2.address, + Amount: '1000000', + FinishAfter: finishAfter, + } + + const createEscrowResponse = await client.submitAndWait(createTx, { + wallet: wallet1, + }) + + console.log(createEscrowResponse) + + // check that the object was actually created + const accountObjectsRequest: AccountObjectsRequest = { + command: 'account_objects', + account: wallet1.classicAddress, + } + + const accountObjects = (await client.request(accountObjectsRequest)).result + .account_objects + + console.log("Escrow object exists in `wallet1`'s account") + console.log(accountObjects) + + // the creator finishes the Escrow using `EscrowFinish` to release the Escrow + const finishTx: EscrowFinish = { + TransactionType: 'EscrowFinish', + Account: wallet1.classicAddress, + Owner: wallet1.classicAddress, + OfferSequence: Number(createEscrowResponse.result.Sequence), + } + + await client.submit(finishTx, { + wallet: wallet1, + }) + + // we see the balances to verify. + console.log('Balances of wallets after Escrow was sent') + console.log( + await client.getXrpBalance(wallet1.classicAddress), + await client.getXrpBalance(wallet2.classicAddress), + ) + + await client.disconnect() +} diff --git a/snippets/src/setRegularKey.ts b/snippets/src/setRegularKey.ts new file mode 100644 index 00000000..f0a8ae0c --- /dev/null +++ b/snippets/src/setRegularKey.ts @@ -0,0 +1,54 @@ +import { Client, Payment, SetRegularKey } from '../../dist/npm' + +const client = new Client('wss://s.altnet.rippletest.net:51233') + +/* + * The snippet walks us through an example usage of RegularKey. + * Later, + */ +async function setRegularKey(): Promise { + await client.connect() + const { wallet: wallet1 } = await client.fundWallet() + const { wallet: wallet2 } = await client.fundWallet() + const { wallet: regularKeyWallet } = await client.fundWallet() + + console.log('Balances before payment') + console.log(await client.getXrpBalance(wallet1.classicAddress)) + console.log(await client.getXrpBalance(wallet2.classicAddress)) + + // assigns key-pair(regularKeyWallet) to wallet1 using `SetRegularKey`. + const tx: SetRegularKey = { + TransactionType: 'SetRegularKey', + Account: wallet1.classicAddress, + RegularKey: regularKeyWallet.classicAddress, + } + const response = await client.submitAndWait(tx, { + wallet: wallet1, + }) + + console.log('Response for successful SetRegularKey tx') + console.log(response) + + /* + * when wallet1 sends payment to wallet2 and + * signs using the regular key wallet, the transaction goes through. + */ + const payment: Payment = { + TransactionType: 'Payment', + Account: wallet1.classicAddress, + Destination: wallet2.classicAddress, + Amount: '1000', + } + + const submitTx = await client.submit(payment, { + wallet: wallet1, + }) + console.log('Response for tx signed using Regular Key:') + console.log(submitTx) + console.log('Balances after payment:') + console.log(await client.getXrpBalance(wallet1.classicAddress)) + console.log(await client.getXrpBalance(wallet2.classicAddress)) + + await client.disconnect() +} +void setRegularKey() diff --git a/src/models/ledger/Ledger.ts b/src/models/ledger/Ledger.ts index a1a91c3f..20c2b3b0 100644 --- a/src/models/ledger/Ledger.ts +++ b/src/models/ledger/Ledger.ts @@ -1,5 +1,4 @@ -import { Transaction } from '../transactions' -import { TransactionMetadata } from '../transactions/metadata' +import { Transaction, TransactionMetadata } from '../transactions' import LedgerEntry from './LedgerEntry' diff --git a/src/models/methods/accountTx.ts b/src/models/methods/accountTx.ts index 5d276857..03ca4b55 100644 --- a/src/models/methods/accountTx.ts +++ b/src/models/methods/accountTx.ts @@ -1,6 +1,5 @@ import { LedgerIndex } from '../common' -import { Transaction } from '../transactions' -import { TransactionMetadata } from '../transactions/metadata' +import { Transaction, TransactionMetadata } from '../transactions' import { BaseRequest, BaseResponse } from './baseMethod' diff --git a/src/models/methods/transactionEntry.ts b/src/models/methods/transactionEntry.ts index 6a16784e..535b3a9c 100644 --- a/src/models/methods/transactionEntry.ts +++ b/src/models/methods/transactionEntry.ts @@ -1,6 +1,5 @@ import { LedgerIndex } from '../common' -import { Transaction } from '../transactions' -import { TransactionMetadata } from '../transactions/metadata' +import { Transaction, TransactionMetadata } from '../transactions' import { BaseRequest, BaseResponse } from './baseMethod' diff --git a/src/models/methods/tx.ts b/src/models/methods/tx.ts index 5cf3c5c2..e971be72 100644 --- a/src/models/methods/tx.ts +++ b/src/models/methods/tx.ts @@ -1,5 +1,4 @@ -import { Transaction } from '../transactions' -import { TransactionMetadata } from '../transactions/metadata' +import { Transaction, TransactionMetadata } from '../transactions' import { BaseRequest, BaseResponse } from './baseMethod' diff --git a/src/models/transactions/index.ts b/src/models/transactions/index.ts index 5da2bde4..b78d2cb9 100644 --- a/src/models/transactions/index.ts +++ b/src/models/transactions/index.ts @@ -1,4 +1,5 @@ export { validate, TransactionAndMetadata, Transaction } from './transaction' +export { TransactionMetadata } from './metadata' export { AccountSetAsfFlags, AccountSetTfFlags, diff --git a/src/utils/hashes/hashLedger.ts b/src/utils/hashes/hashLedger.ts index 510a834c..44d84f7b 100644 --- a/src/utils/hashes/hashLedger.ts +++ b/src/utils/hashes/hashLedger.ts @@ -9,8 +9,7 @@ import { decode, encode } from 'ripple-binary-codec' import { ValidationError, XrplError } from '../../errors' import type { Ledger } from '../../models/ledger' import { LedgerEntry } from '../../models/ledger' -import { Transaction } from '../../models/transactions' -import { TransactionMetadata } from '../../models/transactions/metadata' +import { Transaction, TransactionMetadata } from '../../models/transactions' import HashPrefix from './HashPrefix' import sha512Half from './sha512Half'