This tutorial shows how to use the Quilt library to send money from one ILP account to another (via a STREAM payment) using the ILP Testnet. Complete working code for this tutorial can be found in SendMoneyExample (if you want to use the now-deprecated SimpleStreamSender) or in SendMoneyWithIlpPayExample (to see how payments can be made with the new ILP Pay system).
For this example, we need 2 different accounts on ILP Testnet, a sender and a receiver. You can create accounts using the Generate XRP Credentials button on https://ripplex.io/.
In this example, we will use the following account details (these should be replaced with your own account values):
Sender:
- Username:
demo_user
- Passkey:
OWIyMzUwMzgtNWUzZi00MDU1LWJlMmUtZjk4NjdmMTJlOWYz
- Payment Pointer:
$ripplex.money/demo_user
- Asset code:
XRP
(note: this is actually XRP milli-drops)
Receiver:
- Username:
demo_receiver
- Passkey:
MjI5ODUxZWMtYzA4OC00ZjBkLWI2ZjktOTA4MmYxNGZiODAw
- Payment Pointer:
$ripplex.money/demo_receiver
- Asset code:
XRP
(note: this is actually XRP milli-drops)
Constants:
private static final String SENDER_ACCOUNT_USERNAME = "demo_user";
private static final String SENDER_PASS_KEY = "...";
private static final String RECEIVER_PAYMENT_POINTER = "$ripplex.money/demo_receiver";
private static final String TESTNET_URI = "https://rxprod.wc.wallet.ripplex.io";
The following is an outline of the high-level approach (see actual code below):
- Initialize all required constants and classes.
- Obtain a
Quote
from the payment path using aPaymentOptions
that contains all details about the payment about to be made. - Use the
Quote
to make the payment, yielding aPaymentReceipt
with information about the payment.
First, we create a StreamPacketEncryptionService
using the default AES encryptor (AesGcmStreamSharedSecretCrypto
):
StreamPacketEncryptionService streamPacketEncryptionService = new StreamPacketEncryptionService(
StreamCodecContextFactory.oer(), new AesGcmStreamSharedSecretCrypto()
);
Next, we create an ILP-over-HTTP Link using the credentials defined above (don't forget to set the LinkId
):
Link<?> link = newIlpOverHttpLink(TESTNET_URI, SENDER_AUTH_TOKEN);
link.setLinkId(LinkId.of("ILP Drip Source Account"));
Next, we construct the actual StreamPayer
using an external exchange-rate service that returns faux-rates. In this
case, all identity FX rates will be 1:1
, and USD-to-XRP rates will be 0.25
. This examples makes an XRP-to-XRP payment,
so the FX rate will be 1:1 (i.e., 1.0
).
StreamPayer streamPayer = new StreamPayer.Default(
streamPacketEncryptionService,
link,
new DefaultOracleExchangeRateService(new FauxRateProvider(new BigDecimal("0.25"))
);
Now that everything is initialized, we construct an instance of PaymentOptions
, which contains all inputs
needed by the software to attempt to make a payment:
PaymentOptions paymentOptions = PaymentOptions.builder()
.senderAccountDetails(AccountDetails.builder()
.denomination(Denominations.XRP_MILLI_DROPS)
.interledgerAddress(OPERATOR_ADDRESS)
.build())
.amountToSend(BigDecimal.valueOf(1.50)) // <-- Send 1.5 XRP.
.destinationPaymentPointer(RECEIVER_PAYMENT_POINTER)
.paymentTimeout(Duration.ofSeconds(30)) // <-- Don't wait more than 30s, whether success or failure.
.slippage(Slippage.of(Percentage.of(new BigDecimal("0.10")))) // <-- Allow up to 10% slippage.
.build();
Next, let's obtain a Quote
, which tells us the current conditions of the payment path, and also yields some estimated
outcomes that we can consider before we make our payment:
Quote quote = streamPayer.getQuote(paymentOptions)
.handle((q, throwable) -> {
if (throwable != null) {
LOGGER.error(throwable.getMessage(), throwable); // <-- This is optional, but helpful if something goes wrong.
return null;
}
return q;
})
.join();
Last but not least, we can initiate a payment using the Quote
, like this:
PaymentReceipt paymentReceipt = streamPayer.pay(quote)
.handle((pr, throwable) -> {
if (throwable != null) {
LOGGER.error(throwable.getMessage(), throwable); // <-- This is optional, but helpful if something goes wrong.
return null;
} else {
return pr;
}
}).join();
Finally,we can verify the account balance using curl:
curl --location --request GET 'https://rxprod.wc.wallet.ripplex.io/accounts/demo_receiver/balance' \
--header 'Accept: application/json' \
--header 'Authorization: MjI5ODUxZWMtYzA4OC00ZjBkLWI2ZjktOTA4MmYxNGZiODAw'
Note: This approach has been deprecated in-favor of ILP Pay functionality.
The following is an outline of our high-level approach (see actual code below):
- Fetch the shared secret and destination address. We'll use Quilt's SimpleSpspClient because our Testnet account was created on an ILP node running Interledger4j.
- Create an ILP over HTTP link using Quilt's IlpOverHttpLink.
- Create a STREAM connection using Quilt's SimpleStreamSender
- Send money to the receiver account's payment pointer using the
sendMoney
method onSimpleStreamSender
First, we create an SPSPClient
:
SpspClient spspClient = new SimpleSpspClient();
Now we can fetch a SPSP response which contains the shared secret and destination address for sending payment:
StreamConnectionDetails connectionDetails =
spspClient.getStreamConnectionDetails(PaymentPointer.of(RECEIVER_PAYMENT_POINTER));
We can create a link using the following code:
Link link = new IlpOverHttpLink(
() -> SENDER_ADDRESS,
HttpUrl.parse("https://rxprod.wc.wallet.ripplex.io/accounts/demo_user/ilp"),
newHttpClient(),
new ObjectMapper(),
InterledgerCodecContextFactory.oer(),
new SimpleBearerTokenSupplier(SENDER_ACCOUNT_USERNAME + ":" + SENDER_PASS_KEY));
Using this link
, we can now create a SimpleStreamSender
:
SimpleStreamSender simpleStreamSender = new SimpleStreamSender(link);
Now, we can send a payment for 1000 milli-drops from our source account to our destination account using the shared secret.
SendMoneyRequest sendMoneyRequest = SendMoneyRequest.builder()
.sourceAddress(InterledgerAddress.of(SPSP_SENDER))
.amount(UnsignedLong.valueOf(1000L))
.denomination(Denomination.builder().assetScale((short) 9).assetCode(XRP).build())
.destinationAddress(connectionDetails.destinationAddress())
.sharedSecret(connectionDetails.sharedSecret())
.build();
SendMoneyResult result = simpleStreamSender.sendMoney(sendMoneyRequest).get();
Finally,we can verify the account balance using curl:
curl --location --request GET 'https://rxprod.wc.wallet.ripplex.io/accounts/demo_receiver/balance' \
--header 'Accept: application/json' \
--header 'Authorization: MjI5ODUxZWMtYzA4OC00ZjBkLWI2ZjktOTA4MmYxNGZiODAw'