Skip to content

Commit cd0b690

Browse files
committed
feat(example): add peer-to-peer example
1 parent 0bb6cec commit cd0b690

6 files changed

Lines changed: 284 additions & 84 deletions

File tree

.gitignore

Lines changed: 1 addition & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -41,86 +41,4 @@ dist
4141
**/.terraform
4242

4343
tmp
44-
.spectral.json
45-
styles/.vale-config/3-MDX.ini
46-
styles/Google/Acronyms.yml
47-
styles/Google/AMPM.yml
48-
styles/Google/Colons.yml
49-
styles/Google/Contractions.yml
50-
styles/Google/DateFormat.yml
51-
styles/Google/Ellipses.yml
52-
styles/Google/EmDash.yml
53-
styles/Google/Exclamation.yml
54-
styles/Google/FirstPerson.yml
55-
styles/Google/Gender.yml
56-
styles/Google/GenderBias.yml
57-
styles/Google/HeadingPunctuation.yml
58-
styles/Google/Headings.yml
59-
styles/Google/Latin.yml
60-
styles/Google/LyHyphens.yml
61-
styles/Google/meta.json
62-
styles/Google/OptionalPlurals.yml
63-
styles/Google/Ordinal.yml
64-
styles/Google/OxfordComma.yml
65-
styles/Google/Parens.yml
66-
styles/Google/Passive.yml
67-
styles/Google/Periods.yml
68-
styles/Google/Quotes.yml
69-
styles/Google/Ranges.yml
70-
styles/Google/Semicolons.yml
71-
styles/Google/Slang.yml
72-
styles/Google/Spacing.yml
73-
styles/Google/Spelling.yml
74-
styles/Google/Units.yml
75-
styles/Google/vocab.txt
76-
styles/Google/We.yml
77-
styles/Google/Will.yml
78-
styles/Google/WordList.yml
79-
styles/proselint/Airlinese.yml
80-
styles/proselint/AnimalLabels.yml
81-
styles/proselint/Annotations.yml
82-
styles/proselint/Apologizing.yml
83-
styles/proselint/Archaisms.yml
84-
styles/proselint/But.yml
85-
styles/proselint/Cliches.yml
86-
styles/proselint/CorporateSpeak.yml
87-
styles/proselint/Currency.yml
88-
styles/proselint/Cursing.yml
89-
styles/proselint/DateCase.yml
90-
styles/proselint/DateMidnight.yml
91-
styles/proselint/DateRedundancy.yml
92-
styles/proselint/DateSpacing.yml
93-
styles/proselint/DenizenLabels.yml
94-
styles/proselint/Diacritical.yml
95-
styles/proselint/GenderBias.yml
96-
styles/proselint/GroupTerms.yml
97-
styles/proselint/Hedging.yml
98-
styles/proselint/Hyperbole.yml
99-
styles/proselint/Jargon.yml
100-
styles/proselint/LGBTOffensive.yml
101-
styles/proselint/LGBTTerms.yml
102-
styles/proselint/Malapropisms.yml
103-
styles/proselint/meta.json
104-
styles/proselint/Needless.yml
105-
styles/proselint/Nonwords.yml
106-
styles/proselint/Oxymorons.yml
107-
styles/proselint/P-Value.yml
108-
styles/proselint/RASSyndrome.yml
109-
styles/proselint/README.md
110-
styles/proselint/Skunked.yml
111-
styles/proselint/Spelling.yml
112-
styles/proselint/Typography.yml
113-
styles/proselint/Uncomparables.yml
114-
styles/proselint/Very.yml
115-
styles/write-good/Cliches.yml
116-
styles/write-good/E-Prime.yml
117-
styles/write-good/Illusions.yml
118-
styles/write-good/meta.json
119-
styles/write-good/Passive.yml
120-
styles/write-good/README.md
121-
styles/write-good/So.yml
122-
styles/write-good/ThereIs.yml
123-
styles/write-good/TooWordy.yml
124-
styles/write-good/Weasel.yml
125-
.vale.ini
126-
vale
44+
*.key

examples/peer-to-peer/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Peer-to-Peer Payment Example
2+
3+
This script sends money between two wallet addresses using the [Node Open Payments client](https://github.com/interledger/open-payments-node/tree/main/packages/open-payments).
4+
5+
The script creates an incoming payment on the receiving wallet address, and a quote on the sending wallet address (after getting grants for both). It also creates an interactive outgoing payment grant, which will require user interaction.
6+
7+
The script then finalizes the grant (after it's accepted interactively via the browser), and creates the outgoing payment.
8+
9+
### Steps
10+
11+
1. Make sure you have NodeJS installed
12+
2. Run `pnpm install`
13+
3. Get a private key, client wallet address and keyId from an Open Payments enabled wallet, and add them in the script.
14+
15+
> You can use our [test wallet](https://wallet.interledger-test.dev) to create accounts, and generate developer keys for making payments via the Open Payments APIs. Instructions about how to use the test wallet are found at the [Open Payments API documentation](https://openpayments.dev/sdk/before-you-begin/).
16+
17+
4. Pick a receiving wallet address, a sending wallet address, and add them as variables in the script.
18+
5. Run `node index.js`
19+
6. Click on the outputted URL, to accept the outgoing payment grant.
20+
7. Return to the terminal, hit enter. After this, the script will create the outgoing payment and funds will move between the receiver and the sender!

examples/peer-to-peer/index.js

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/**
2+
* This script sets up an incoming payment on a receiving wallet address,
3+
* and a quote on the sending wallet address (after getting grants for both of the resources).
4+
*
5+
* The final step is asking for an outgoing payment grant for the sending wallet address.
6+
* Since this needs user interaction, you will need to navigate to the URL, and accept the interactive grant.
7+
*
8+
* To start, please add the variables for configuring the client & the wallet addresses for the payment.
9+
*/
10+
11+
import {
12+
createAuthenticatedClient,
13+
OpenPaymentsClientError,
14+
isFinalizedGrant
15+
} from '@interledger/open-payments'
16+
import readline from 'readline/promises'
17+
;(async () => {
18+
// Client configuration
19+
const PRIVATE_KEY_PATH = 'private.key'
20+
const KEY_ID = ''
21+
22+
// Make sure the wallet addresses starts with https:// (not $)
23+
const CLIENT_WALLET_ADDRESS_URL = ''
24+
const SENDING_WALLET_ADDRESS_URL = ''
25+
const RECEIVING_WALLET_ADDRESS_URL = ''
26+
27+
const client = await createAuthenticatedClient({
28+
walletAddressUrl: CLIENT_WALLET_ADDRESS_URL,
29+
keyId: KEY_ID,
30+
privateKey: PRIVATE_KEY_PATH
31+
})
32+
33+
// Step 1: Get the sending and receiving wallet addresses
34+
const sendingWalletAddress = await client.walletAddress.get({
35+
url: SENDING_WALLET_ADDRESS_URL
36+
})
37+
const receivingWalletAddress = await client.walletAddress.get({
38+
url: RECEIVING_WALLET_ADDRESS_URL
39+
})
40+
41+
console.log('\nStep 1: got wallet addresses', {
42+
receivingWalletAddress,
43+
sendingWalletAddress
44+
})
45+
46+
// Step 2: Get a grant for the incoming payment, so we can create the incoming payment on the receiving wallet address
47+
const incomingPaymentGrant = await client.grant.request(
48+
{
49+
url: receivingWalletAddress.authServer
50+
},
51+
{
52+
access_token: {
53+
access: [
54+
{
55+
type: 'incoming-payment',
56+
actions: ['read', 'complete', 'create']
57+
}
58+
]
59+
}
60+
}
61+
)
62+
63+
console.log(
64+
'\nStep 2: got incoming payment grant for receiving wallet address',
65+
incomingPaymentGrant
66+
)
67+
68+
if (!isFinalizedGrant(incomingPaymentGrant)) {
69+
throw new Error('Expected finalized incoming payment grant')
70+
}
71+
72+
// Step 3: Create the incoming payment. This will be where funds will be received.
73+
const incomingPayment = await client.incomingPayment.create(
74+
{
75+
url: receivingWalletAddress.resourceServer,
76+
accessToken: incomingPaymentGrant.access_token.value
77+
},
78+
{
79+
walletAddress: receivingWalletAddress.id,
80+
incomingAmount: {
81+
assetCode: receivingWalletAddress.assetCode,
82+
assetScale: receivingWalletAddress.assetScale,
83+
value: '1000'
84+
}
85+
}
86+
)
87+
88+
console.log(
89+
'\nStep 3: created incoming payment on receiving wallet address',
90+
incomingPayment
91+
)
92+
93+
// Step 4: Get a quote grant, so we can create a quote on the sending wallet address
94+
const quoteGrant = await client.grant.request(
95+
{
96+
url: sendingWalletAddress.authServer
97+
},
98+
{
99+
access_token: {
100+
access: [
101+
{
102+
type: 'quote',
103+
actions: ['create', 'read']
104+
}
105+
]
106+
}
107+
}
108+
)
109+
110+
if (!isFinalizedGrant(quoteGrant)) {
111+
throw new Error('Expected finalized quote grant')
112+
}
113+
114+
console.log('\nStep 4: got quote grant on sending wallet address', quoteGrant)
115+
116+
// Step 5: Create a quote, this gives an indication of how much it will cost to pay into the incoming payment
117+
const quote = await client.quote.create(
118+
{
119+
url: sendingWalletAddress.resourceServer,
120+
accessToken: quoteGrant.access_token.value
121+
},
122+
{
123+
walletAddress: sendingWalletAddress.id,
124+
receiver: incomingPayment.id,
125+
method: 'ilp'
126+
}
127+
)
128+
129+
console.log('\nStep 5: got quote on sending wallet address', quote)
130+
131+
// Step 7: Start the grant process for the outgoing payments.
132+
// This is an interactive grant: the user (in this case, you) will need to accept the grant by navigating to the outputted link.
133+
const outgoingPaymentGrant = await client.grant.request(
134+
{
135+
url: sendingWalletAddress.authServer
136+
},
137+
{
138+
access_token: {
139+
access: [
140+
{
141+
type: 'outgoing-payment',
142+
actions: ['read', 'create'],
143+
limits: {
144+
debitAmount: {
145+
assetCode: quote.debitAmount.assetCode,
146+
assetScale: quote.debitAmount.assetScale,
147+
value: quote.debitAmount.value
148+
}
149+
},
150+
identifier: sendingWalletAddress.id
151+
}
152+
]
153+
},
154+
interact: {
155+
start: ['redirect']
156+
// finish: {
157+
// method: "redirect",
158+
// // This is where you can (optionally) redirect a user to after going through interaction.
159+
// // Keep in mind, you will need to parse the interact_ref in the resulting interaction URL,
160+
// // and pass it into the grant continuation request.
161+
// uri: "https://example.com",
162+
// nonce: crypto.randomUUID(),
163+
// },
164+
}
165+
}
166+
)
167+
168+
console.log(
169+
'\nStep 7: got pending outgoing payment grant',
170+
outgoingPaymentGrant
171+
)
172+
console.log(
173+
'Please navigate to the following URL, to accept the interaction from the sending wallet:'
174+
)
175+
console.log(outgoingPaymentGrant.interact.redirect)
176+
177+
await readline
178+
.createInterface({ input: process.stdin, output: process.stdout })
179+
.question('\nPlease accept grant and press enter...')
180+
181+
let finalizedOutgoingPaymentGrant
182+
183+
const grantContinuationErrorMessage =
184+
'\nThere was an error continuing the grant. You probably have not accepted the grant at the url (or it has already been used up, in which case, rerun the script).'
185+
186+
try {
187+
finalizedOutgoingPaymentGrant = await client.grant.continue({
188+
url: outgoingPaymentGrant.continue.uri,
189+
accessToken: outgoingPaymentGrant.continue.access_token.value
190+
})
191+
} catch (err) {
192+
if (err instanceof OpenPaymentsClientError) {
193+
console.log(grantContinuationErrorMessage)
194+
process.exit()
195+
}
196+
197+
throw err
198+
}
199+
200+
if (!isFinalizedGrant(finalizedOutgoingPaymentGrant)) {
201+
console.log(
202+
'There was an error continuing the grant. You probably have not accepted the grant at the url.'
203+
)
204+
process.exit()
205+
}
206+
207+
console.log(
208+
'\nStep 6: got finalized outgoing payment grant',
209+
finalizedOutgoingPaymentGrant
210+
)
211+
212+
// Step 7: Finally, create the outgoing payment on the sending wallet address.
213+
// This will make a payment from the outgoing payment to the incoming one (over ILP)
214+
const outgoingPayment = await client.outgoingPayment.create(
215+
{
216+
url: sendingWalletAddress.resourceServer,
217+
accessToken: finalizedOutgoingPaymentGrant.access_token.value
218+
},
219+
{
220+
walletAddress: sendingWalletAddress.id,
221+
quoteId: quote.id
222+
}
223+
)
224+
225+
console.log(
226+
'\nStep 7: Created outgoing payment. Funds will now move from the outgoing payment to the incoming payment.',
227+
outgoingPayment
228+
)
229+
230+
process.exit()
231+
})()

examples/peer-to-peer/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "example-peer-to-peer",
3+
"private": "true",
4+
"version": "1.0.0",
5+
"description": "",
6+
"main": "index.js",
7+
"type": "module",
8+
"scripts": {
9+
"start": "node index.js"
10+
},
11+
"keywords": [],
12+
"author": "",
13+
"license": "ISC",
14+
"dependencies": {
15+
"@interledger/open-payments": "workspace:^",
16+
"readline": "^1.3.0"
17+
}
18+
}

pnpm-lock.yaml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
packages:
22
- 'packages/*'
3-
- 'docs'
3+
- 'examples/*'

0 commit comments

Comments
 (0)