From 9296384ada0d2f9312f5e5829167fd6b89e287b6 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Thu, 29 Aug 2024 11:55:54 -0700 Subject: [PATCH 1/4] update Paths snippet test: perform prerequisite test setup within the test, remove dependence on theexternal state of testnet --- packages/xrpl/snippets/src/paths.ts | 153 ++++++++++++++++++++++------ 1 file changed, 122 insertions(+), 31 deletions(-) diff --git a/packages/xrpl/snippets/src/paths.ts b/packages/xrpl/snippets/src/paths.ts index cd16273a42..6633e903ac 100644 --- a/packages/xrpl/snippets/src/paths.ts +++ b/packages/xrpl/snippets/src/paths.ts @@ -1,50 +1,141 @@ -import { Client, Payment } from '../../src' +import { Client, IssuedCurrencyAmount, AccountSetAsfFlags } from '../../src' -const client = new Client('wss://s.altnet.rippletest.net:51233') +// Note: This test is inspired from a unit test titled `indirect_paths_path_find` in the +// rippled C++ codebase (Path_test.cpp) +// https://github.com/XRPLF/rippled/blob/d9bd75e68326861fb38fd5b27d47da1054a7fc3b/src/test/app/Path_test.cpp#L683 -async function createTxWithPaths(): Promise { +async function runTest() { + // Create a client to connect to the test network + const client = new Client('wss://s.altnet.rippletest.net:51233') await client.connect() - const { wallet } = await client.fundWallet(null, { - usageContext: 'code snippets', - }) - const destination_account = 'rKT4JX4cCof6LcDYRz8o3rGRu7qxzZ2Zwj' - const destination_amount = { - value: '0.001', - currency: 'USD', - issuer: 'rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc', - } + // Creating wallets to send money from + // these wallets will have 100 testnet XRP + const { wallet: alice } = await client.fundWallet() + const { wallet: bob } = await client.fundWallet() + const { wallet: carol } = await client.fundWallet() + + // send AccountSet transaction with asfDefaultRipple turned on + // this enables rippling on all trustlines through these accounts. + + await client.submitAndWait( + { + TransactionType: 'AccountSet', + Account: alice.classicAddress, + SetFlag: AccountSetAsfFlags.asfDefaultRipple, + }, + { + wallet: alice, + }, + ) + + await client.submitAndWait( + { + TransactionType: 'AccountSet', + Account: bob.classicAddress, + SetFlag: AccountSetAsfFlags.asfDefaultRipple, + }, + { + wallet: bob, + }, + ) + + await client.submitAndWait( + { + TransactionType: 'AccountSet', + Account: carol.classicAddress, + SetFlag: AccountSetAsfFlags.asfDefaultRipple, + }, + { + wallet: carol, + }, + ) + + // set up trustlines from bob -> alice, carol -> bob to transfer IssuedCurrency `USD` + await client.submitAndWait( + { + TransactionType: 'TrustSet', + Account: bob.classicAddress, + LimitAmount: { + currency: 'USD', + issuer: alice.classicAddress, + value: '1000', + }, + }, + { + wallet: bob, + }, + ) - const resp = await client.request({ - // TOOD: Replace with path_find - https://github.com/XRPLF/xrpl.js/issues/2385 + await client.submitAndWait( + { + TransactionType: 'TrustSet', + Account: carol.classicAddress, + LimitAmount: { + currency: 'USD', + issuer: bob.classicAddress, + value: '1000', + }, + }, + { + wallet: carol, + }, + ) + + // Perform path find + // Note: Rippling allows IssuedCurrencies with identical currency-codes, + // but different (ex: alice, bob and carol) issuers to settle their transfers. + // Docs: https://xrpl.org/docs/concepts/tokens/fungible-tokens/rippling + const response = await client.request({ command: 'ripple_path_find', - source_account: wallet.classicAddress, + source_account: alice.classicAddress, source_currencies: [ { - currency: 'XRP', + currency: 'USD', + issuer: alice.classicAddress, }, ], - destination_account, - destination_amount, + destination_account: carol.classicAddress, + destination_amount: { + currency: 'USD', + issuer: carol.classicAddress, + value: '5', + }, }) - console.log(resp) - const paths = resp.result.alternatives[0].paths_computed - console.log(paths) + // Check the results + const paths = response.result.alternatives + if (paths.length === 0) { + throw new Error('No paths found') + } + + console.log('Paths discovered by ripple_path_find RPC:') + console.log(JSON.stringify(paths, null, 2)) + + // Check if the path includes bob + const path = paths[0].paths_computed[0][0] + if (path.account !== bob.classicAddress) { + throw new Error('Path does not include bob') + } + + // Check the source amount - const tx: Payment = { - TransactionType: 'Payment', - Account: wallet.classicAddress, - Amount: destination_amount, - Destination: destination_account, - Paths: paths, + // Check if the path includes bob + // the "paths_computed" field uses a 2-D matrix representation as detailed here: + // https://xrpl.org/docs/concepts/tokens/fungible-tokens/paths#path-specifications + const sourceAmount = paths[0].source_amount as IssuedCurrencyAmount + if ( + sourceAmount.currency !== 'USD' || + sourceAmount.issuer !== alice.classicAddress || + parseFloat(sourceAmount.value) !== 5.0 + ) { + throw new Error('Unexpected source amount') } - await client.autofill(tx) - const signed = wallet.sign(tx) - console.log('signed:', signed) + console.log('Test passed successfully!') + // Disconnect from the client await client.disconnect() } -void createTxWithPaths() +runTest().catch(console.error) From edeec19e90692fe9680cd4ea48b3bb4debbffbae Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Thu, 29 Aug 2024 12:09:12 -0700 Subject: [PATCH 2/4] fix lint errors --- packages/xrpl/snippets/src/paths.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/xrpl/snippets/src/paths.ts b/packages/xrpl/snippets/src/paths.ts index 6633e903ac..62704b5fd7 100644 --- a/packages/xrpl/snippets/src/paths.ts +++ b/packages/xrpl/snippets/src/paths.ts @@ -4,7 +4,7 @@ import { Client, IssuedCurrencyAmount, AccountSetAsfFlags } from '../../src' // rippled C++ codebase (Path_test.cpp) // https://github.com/XRPLF/rippled/blob/d9bd75e68326861fb38fd5b27d47da1054a7fc3b/src/test/app/Path_test.cpp#L683 -async function runTest() { +async function runTest(): Promise { // Create a client to connect to the test network const client = new Client('wss://s.altnet.rippletest.net:51233') await client.connect() @@ -113,6 +113,8 @@ async function runTest() { console.log(JSON.stringify(paths, null, 2)) // Check if the path includes bob + // the "paths_computed" field uses a 2-D matrix representation as detailed here: + // https://xrpl.org/docs/concepts/tokens/fungible-tokens/paths#path-specifications const path = paths[0].paths_computed[0][0] if (path.account !== bob.classicAddress) { throw new Error('Path does not include bob') @@ -120,13 +122,12 @@ async function runTest() { // Check the source amount - // Check if the path includes bob - // the "paths_computed" field uses a 2-D matrix representation as detailed here: - // https://xrpl.org/docs/concepts/tokens/fungible-tokens/paths#path-specifications + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- result currency will be USD const sourceAmount = paths[0].source_amount as IssuedCurrencyAmount if ( sourceAmount.currency !== 'USD' || sourceAmount.issuer !== alice.classicAddress || + // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 5 is an arbitrarily chosen amount for this test parseFloat(sourceAmount.value) !== 5.0 ) { throw new Error('Unexpected source amount') From 3e57f546370f4e2271f3b48ebf0f45a9fbd4ca3d Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 9 Sep 2024 14:31:35 -0700 Subject: [PATCH 3/4] remove bridge snippet test, the sidechain has been shut down --- packages/xrpl/snippets/src/bridgeTransfer.ts | 170 ------------------- 1 file changed, 170 deletions(-) delete mode 100644 packages/xrpl/snippets/src/bridgeTransfer.ts diff --git a/packages/xrpl/snippets/src/bridgeTransfer.ts b/packages/xrpl/snippets/src/bridgeTransfer.ts deleted file mode 100644 index 59421d1aa0..0000000000 --- a/packages/xrpl/snippets/src/bridgeTransfer.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* eslint-disable max-depth -- needed for attestation checking */ -/* eslint-disable @typescript-eslint/consistent-type-assertions -- needed here */ -/* eslint-disable no-await-in-loop -- needed here */ -import { - AccountObjectsRequest, - LedgerEntry, - Client, - XChainAccountCreateCommit, - XChainBridge, - XChainCommit, - XChainCreateClaimID, - xrpToDrops, - Wallet, - getXChainClaimID, -} from '../../src' - -async function sleep(sec: number): Promise { - return new Promise((resolve) => { - setTimeout(resolve, sec * 1000) - }) -} - -const lockingClient = new Client('wss://s.devnet.rippletest.net:51233') -const issuingClient = new Client( - 'wss://sidechain-net2.devnet.rippletest.net:51233', -) -const MAX_LEDGERS_WAITED = 5 -const LEDGER_CLOSE_TIME = 4 - -void bridgeTransfer() - -async function bridgeTransfer(): Promise { - await lockingClient.connect() - await issuingClient.connect() - const lockingChainDoor = 'rnQAXXWoFNN6PEqwqsdTngCtFPCrmfuqFJ' - - const accountObjectsRequest: AccountObjectsRequest = { - command: 'account_objects', - account: lockingChainDoor, - type: 'bridge', - } - const lockingAccountObjects = ( - await lockingClient.request(accountObjectsRequest) - ).result.account_objects - // There will only be one here - a door account can only have one bridge per currency - const bridgeData = lockingAccountObjects.filter( - (obj) => - obj.LedgerEntryType === 'Bridge' && - obj.XChainBridge.LockingChainIssue.currency === 'XRP', - )[0] as LedgerEntry.Bridge - const bridge: XChainBridge = bridgeData.XChainBridge - console.log(bridge) - - console.log('Creating wallet on the locking chain via the faucet...') - const { wallet: wallet1 } = await lockingClient.fundWallet() - console.log(wallet1) - const wallet2 = Wallet.generate() - - console.log( - `Creating ${wallet2.classicAddress} on the issuing chain via the bridge...`, - ) - - const fundTx: XChainAccountCreateCommit = { - TransactionType: 'XChainAccountCreateCommit', - Account: wallet1.classicAddress, - XChainBridge: bridge, - SignatureReward: bridgeData.SignatureReward, - Destination: wallet2.classicAddress, - Amount: ( - parseInt(bridgeData.MinAccountCreateAmount as string, 10) * 2 - ).toString(), - } - const fundResponse = await lockingClient.submitAndWait(fundTx, { - wallet: wallet1, - }) - console.log(fundResponse) - - console.log( - 'Waiting for the attestation to go through... (usually 8-12 seconds)', - ) - let ledgersWaited = 0 - let initialBalance = 0 - while (ledgersWaited < MAX_LEDGERS_WAITED) { - await sleep(LEDGER_CLOSE_TIME) - try { - initialBalance = await issuingClient.getXrpBalance(wallet2.classicAddress) - console.log( - `Wallet ${wallet2.classicAddress} has been funded with a balance of ${initialBalance} XRP`, - ) - break - } catch (_error) { - ledgersWaited += 1 - if (ledgersWaited === MAX_LEDGERS_WAITED) { - // This error should never be hit if the bridge is running - throw Error('Destination account creation via the bridge failed.') - } - } - } - - console.log( - `Transferring funds from ${wallet1.classicAddress} on the locking chain to ` + - `${wallet2.classicAddress} on the issuing_chain...`, - ) - - // Fetch the claim ID for the transfer - console.log('Step 1: Fetching the claim ID for the transfer...') - const claimIdTx: XChainCreateClaimID = { - TransactionType: 'XChainCreateClaimID', - Account: wallet2.classicAddress, - XChainBridge: bridge, - SignatureReward: bridgeData.SignatureReward, - OtherChainSource: wallet1.classicAddress, - } - const claimIdResult = await issuingClient.submitAndWait(claimIdTx, { - wallet: wallet2, - }) - console.log(claimIdResult) - - // Extract new claim ID from metadata - const xchainClaimId = getXChainClaimID(claimIdResult.result.meta) - if (xchainClaimId == null) { - // This shouldn't trigger assuming the transaction succeeded - throw Error('Could not extract XChainClaimID') - } - - console.log(`Claim ID for the transfer: ${xchainClaimId}`) - - console.log( - 'Step 2: Locking the funds on the locking chain with an XChainCommit transaction...', - ) - const commitTx: XChainCommit = { - TransactionType: 'XChainCommit', - Account: wallet1.classicAddress, - Amount: xrpToDrops(1), - XChainBridge: bridge, - XChainClaimID: xchainClaimId, - OtherChainDestination: wallet2.classicAddress, - } - const commitResult = await lockingClient.submitAndWait(commitTx, { - wallet: wallet1, - }) - console.log(commitResult) - - console.log( - 'Waiting for the attestation to go through... (usually 8-12 seconds)', - ) - ledgersWaited = 0 - while (ledgersWaited < MAX_LEDGERS_WAITED) { - await sleep(LEDGER_CLOSE_TIME) - const currentBalance = await issuingClient.getXrpBalance( - wallet2.classicAddress, - ) - console.log(initialBalance, currentBalance) - if (currentBalance > initialBalance) { - console.log('Transfer is complete') - console.log( - `New balance of ${wallet2.classicAddress} is ${currentBalance} XRP`, - ) - break - } - - ledgersWaited += 1 - if (ledgersWaited === MAX_LEDGERS_WAITED) { - throw Error('Bridge transfer failed.') - } - } - - await lockingClient.disconnect() - await issuingClient.disconnect() -} From bfe1cf6853e075ca4b9c07c53bc7db02f844d9da Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Thu, 19 Sep 2024 09:18:19 -0700 Subject: [PATCH 4/4] [FIX] Update paths snippet. Replace RipplePathFind with PathFind RPC (fixes #2385) --- packages/xrpl/snippets/src/paths.ts | 163 ++++++---------------------- 1 file changed, 36 insertions(+), 127 deletions(-) diff --git a/packages/xrpl/snippets/src/paths.ts b/packages/xrpl/snippets/src/paths.ts index 62704b5fd7..499a18a0ba 100644 --- a/packages/xrpl/snippets/src/paths.ts +++ b/packages/xrpl/snippets/src/paths.ts @@ -1,142 +1,51 @@ -import { Client, IssuedCurrencyAmount, AccountSetAsfFlags } from '../../src' +import { Client, Payment } from '../../src' -// Note: This test is inspired from a unit test titled `indirect_paths_path_find` in the -// rippled C++ codebase (Path_test.cpp) -// https://github.com/XRPLF/rippled/blob/d9bd75e68326861fb38fd5b27d47da1054a7fc3b/src/test/app/Path_test.cpp#L683 +// Prerequisites for this snippet. Please verify these conditions after a reset of the +// test network: +// - destination_account must have a trust line with the destination_amount.issuer +// - There must be appropriate DEX Offers or XRP/TST AMM for the cross-currency exchange -async function runTest(): Promise { - // Create a client to connect to the test network - const client = new Client('wss://s.altnet.rippletest.net:51233') - await client.connect() - - // Creating wallets to send money from - // these wallets will have 100 testnet XRP - const { wallet: alice } = await client.fundWallet() - const { wallet: bob } = await client.fundWallet() - const { wallet: carol } = await client.fundWallet() - - // send AccountSet transaction with asfDefaultRipple turned on - // this enables rippling on all trustlines through these accounts. - - await client.submitAndWait( - { - TransactionType: 'AccountSet', - Account: alice.classicAddress, - SetFlag: AccountSetAsfFlags.asfDefaultRipple, - }, - { - wallet: alice, - }, - ) - - await client.submitAndWait( - { - TransactionType: 'AccountSet', - Account: bob.classicAddress, - SetFlag: AccountSetAsfFlags.asfDefaultRipple, - }, - { - wallet: bob, - }, - ) - - await client.submitAndWait( - { - TransactionType: 'AccountSet', - Account: carol.classicAddress, - SetFlag: AccountSetAsfFlags.asfDefaultRipple, - }, - { - wallet: carol, - }, - ) +// PathFind RPC requires the use of a Websocket client only +const client = new Client('wss://s.altnet.rippletest.net:51233') - // set up trustlines from bob -> alice, carol -> bob to transfer IssuedCurrency `USD` - await client.submitAndWait( - { - TransactionType: 'TrustSet', - Account: bob.classicAddress, - LimitAmount: { - currency: 'USD', - issuer: alice.classicAddress, - value: '1000', - }, - }, - { - wallet: bob, - }, - ) - - await client.submitAndWait( - { - TransactionType: 'TrustSet', - Account: carol.classicAddress, - LimitAmount: { - currency: 'USD', - issuer: bob.classicAddress, - value: '1000', - }, - }, - { - wallet: carol, - }, - ) +async function createTxWithPaths(): Promise { + await client.connect() - // Perform path find - // Note: Rippling allows IssuedCurrencies with identical currency-codes, - // but different (ex: alice, bob and carol) issuers to settle their transfers. - // Docs: https://xrpl.org/docs/concepts/tokens/fungible-tokens/rippling - const response = await client.request({ - command: 'ripple_path_find', - source_account: alice.classicAddress, - source_currencies: [ - { - currency: 'USD', - issuer: alice.classicAddress, - }, - ], - destination_account: carol.classicAddress, - destination_amount: { - currency: 'USD', - issuer: carol.classicAddress, - value: '5', - }, + const { wallet } = await client.fundWallet(null, { + usageContext: 'code snippets', }) - - // Check the results - const paths = response.result.alternatives - if (paths.length === 0) { - throw new Error('No paths found') + const destination_account = 'rJPeZVPty1bXXbDR9oKscg2irqABr7sP3t' + const destination_amount = { + value: '0.001', + currency: 'TST', + issuer: 'rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd', } - console.log('Paths discovered by ripple_path_find RPC:') - console.log(JSON.stringify(paths, null, 2)) - - // Check if the path includes bob - // the "paths_computed" field uses a 2-D matrix representation as detailed here: - // https://xrpl.org/docs/concepts/tokens/fungible-tokens/paths#path-specifications - const path = paths[0].paths_computed[0][0] - if (path.account !== bob.classicAddress) { - throw new Error('Path does not include bob') - } + const resp = await client.request({ + command: 'path_find', + subcommand: 'create', + source_account: wallet.classicAddress, + destination_account, + destination_amount, + }) + console.log(resp) - // Check the source amount + const paths = resp.result.alternatives[0].paths_computed + console.log(paths) - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- result currency will be USD - const sourceAmount = paths[0].source_amount as IssuedCurrencyAmount - if ( - sourceAmount.currency !== 'USD' || - sourceAmount.issuer !== alice.classicAddress || - // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 5 is an arbitrarily chosen amount for this test - parseFloat(sourceAmount.value) !== 5.0 - ) { - throw new Error('Unexpected source amount') + const tx: Payment = { + TransactionType: 'Payment', + Account: wallet.classicAddress, + Amount: destination_amount, + Destination: destination_account, + Paths: paths, } - console.log('Test passed successfully!') + await client.autofill(tx) + const signed = wallet.sign(tx) + console.log('signed:', signed) - // Disconnect from the client await client.disconnect() } -runTest().catch(console.error) +void createTxWithPaths()