Skip to content
This repository has been archived by the owner on Aug 26, 2024. It is now read-only.

Commit

Permalink
adding in stored balances
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelneale committed Jun 7, 2024
1 parent bde9637 commit 4424068
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 22 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"example-create-issuer": "npm run _debug -- dist/example/create-issuer.js",
"example-issue-credential": "npm run _debug -- dist/example/issue-credential.js",
"example-e2e-exchange": "npm run _debug -- dist/example/full-tbdex-exchange.js",
"example-stored-balance": "npm run _debug -- dist/example/full-stored-balances.js",
"lint": "eslint . --ext .ts --max-warnings 0",
"lint:fix": "eslint . --ext .ts --fix",
"seed-offerings": "npm run _start -- dist/seed-offerings.js",
Expand Down
16 changes: 14 additions & 2 deletions src/db/balances-repository.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import type { BalancesApi } from '@tbdex/http-server'
import type { BalancesApi, Quote } from '@tbdex/http-server'
import { Balance } from '@tbdex/http-server'
import { config } from '../config.js'

let available = '1000'

export class _BalancesRepository implements BalancesApi {
async getBalances({ requesterDid }): Promise<Balance[]> {
console.log('getBalances for:', requesterDid)
const bal = Balance.create({
data: {
currencyCode: 'USDC',
available: '1000',
available: available,
},
metadata: {
from: config.pfiDid.uri,
}
})
return [bal]
}

withdraw(quote: Quote) {
// substract this from available
available = (parseFloat(available) - parseFloat(quote.data.payout.amount)).toString()
}

deposit(quote: Quote) {
// add this to available
available = (parseFloat(available) + parseFloat(quote.data.payin.amount)).toString()
}
}

export const BalancesRepository = new _BalancesRepository()
115 changes: 95 additions & 20 deletions src/db/exchange-repository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Message, Close, Order, OrderStatus, Quote, ExchangesApi, Rfq, Parser, Exchange } from '@tbdex/http-server'
import { Message, Close, Order, OrderStatus, Quote, ExchangesApi, Rfq, Parser, Exchange, Offering } from '@tbdex/http-server'
import type { MessageModel, MessageKind, GetExchangesFilter } from '@tbdex/http-server'
import { OfferingRepository } from './offering-repository.js'
import { Postgres } from './postgres.js'
import { config } from '../config.js'
import { BalancesRepository } from './balances-repository.js'

class _ExchangeRepository implements ExchangesApi {

Expand Down Expand Up @@ -77,6 +79,8 @@ class _ExchangeRepository implements ExchangesApi {
return await this.getMessage({ exchangeId: opts.exchangeId, messageKind: 'rfq' }) as Rfq
}



async getQuote(opts: { exchangeId: string }): Promise<Quote> {
return await this.getMessage({ exchangeId: opts.exchangeId, messageKind: 'quote' }) as Quote
}
Expand Down Expand Up @@ -123,6 +127,7 @@ class _ExchangeRepository implements ExchangesApi {
}
}


async addMessage(opts: { message: Message }) {
const { message } = opts
const subject = aliceMessageKinds.has(message.kind) ? message.from : message.to
Expand All @@ -140,25 +145,17 @@ class _ExchangeRepository implements ExchangesApi {
console.log(`Add ${message.kind} Result: ${JSON.stringify(result, null, 2)}`)

if (message.kind == 'rfq') {
const quote = Quote.create({
metadata: {
from: config.pfiDid.uri,
to: message.from,
exchangeId: message.exchangeId
},
data: {
expiresAt: new Date(new Date().getTime() + 60 * 60000).toISOString(),
payin: {
currencyCode: 'BTC',
amount: '1000.00'
},
payout: {
currencyCode: 'KES',
amount: '123456789.00'
}
}
})
await quote.sign(config.pfiDid)
const rfq = message as Rfq
let quote: Quote
if (rfq.data.payin.kind == 'STORED_BALANCE' && rfq.data.payout.kind == 'WIRE_TRANSFER') {
quote = await this.withdrawalQuote(rfq)
}
if (rfq.data.payin.kind == 'WIRE_TRANSFER' && rfq.data.payout.kind == 'STORED_BALANCE') {
quote = await this.depositQuote(rfq)
}
else {
quote = await this.BtcKesQuote(rfq)
}
this.addMessage({ message: quote as Quote})
}

Expand All @@ -176,6 +173,13 @@ class _ExchangeRepository implements ExchangesApi {
await orderStatus.sign(config.pfiDid)
this.addMessage({ message: orderStatus as OrderStatus})

const quote = await this.getQuote({ exchangeId: message.exchangeId })
if (quote.data.payout.currencyCode == 'STORED_BALANCE') {
BalancesRepository.deposit(quote)
} else if (quote.data.payin.currencyCode == 'STORED_BALANCE') {
BalancesRepository.withdraw(quote)
}

await new Promise(resolve => setTimeout(resolve, 1000)) // 1 second delay

orderStatus = OrderStatus.create({
Expand Down Expand Up @@ -207,6 +211,77 @@ class _ExchangeRepository implements ExchangesApi {
this.addMessage({ message: close as Close })
}
}


private async depositQuote(rfq: Rfq) {
const quote = Quote.create({
metadata: {
from: config.pfiDid.uri,
to: rfq.from,
exchangeId: rfq.exchangeId
},
data: {
expiresAt: new Date(new Date().getTime() + 60 * 60000).toISOString(),
payin: {
currencyCode: 'WIRE_TRANSFER',
amount: rfq.data.payin.amount
},
payout: {
currencyCode: 'STORED_BALANCE',
amount: rfq.data.payin.amount
}
}
})
await quote.sign(config.pfiDid)
return quote

}

private async withdrawalQuote(rfq: Rfq) {
const quote = Quote.create({
metadata: {
from: config.pfiDid.uri,
to: rfq.from,
exchangeId: rfq.exchangeId
},
data: {
expiresAt: new Date(new Date().getTime() + 60 * 60000).toISOString(),
payin: {
currencyCode: 'STORED_BALANCE',
amount: rfq.data.payin.amount
},
payout: {
currencyCode: 'WIRE_TRANSFER',
amount: rfq.data.payin.amount
}
}
})
await quote.sign(config.pfiDid)
return quote
}

private async BtcKesQuote(rfq: Rfq) {
const quote = Quote.create({
metadata: {
from: config.pfiDid.uri,
to: rfq.from,
exchangeId: rfq.exchangeId
},
data: {
expiresAt: new Date(new Date().getTime() + 60 * 60000).toISOString(),
payin: {
currencyCode: 'BTC',
amount: '1000.00'
},
payout: {
currencyCode: 'KES',
amount: '123456789.00'
}
}
})
await quote.sign(config.pfiDid)
return quote
}
}

const aliceMessageKinds = new Set(['rfq', 'order'])
Expand Down
140 changes: 140 additions & 0 deletions src/example/full-stored-balances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import {
TbdexHttpClient,
Rfq,
Quote,
Order,
OrderStatus,
Close,
} from '@tbdex/http-client'
import { createOrLoadDid } from './utils.js'
import { BearerDid } from '@web5/dids'
import fs from 'fs'


// load pfiDid from pfiDid.txt
const pfiDid = fs.readFileSync('pfiDid.txt', 'utf-8').trim()


const signedCredential = fs.readFileSync('signedCredential.txt', 'utf-8').trim()

//
// Connect to the PFI and get the list of offerings (offerings are resources - anyone can ask for them)
//
const offerings = await TbdexHttpClient.getOfferings({ pfiDid: pfiDid })
console.log('got offerings:', JSON.stringify(offerings, null, 2))


//
// Load alice's private key to sign RFQ
//
const alice = await createOrLoadDid('alice.json')

const [balances] = await TbdexHttpClient.getBalances({ pfiDid: pfiDid, did: alice })
console.log('got balances:', JSON.stringify(balances, null, 2))

// lets make a deposit
let offering = offerings[1]
console.log('deposit offering', offering)
const rfq = Rfq.create({
metadata: { from: alice.uri, to: pfiDid },
data: {
offeringId: offerings[1].id,
payin: {
kind: 'WIRE_TRANSFER',
amount: '100.00',
paymentDetails: {},
},
payout: {
kind: 'STORED_BALANCE',
paymentDetails: {},
},
claims: [], // no claims for now - will add in kcc soon
},
})

await rfq.sign(alice)

try {
await TbdexHttpClient.createExchange(rfq)
} catch (error) {
console.log('Can\'t create:', error)
}
console.log('sent RFQ: ', JSON.stringify(rfq, null, 2))

let quote

//Wait for Quote message to appear in the exchange
while (!quote) {
const exchange = await TbdexHttpClient.getExchange({
pfiDid: pfiDid,
did: alice,
exchangeId: rfq.exchangeId
})

quote = exchange.find(msg => msg instanceof Quote)

if (!quote) {
// Wait 2 seconds before making another request
await new Promise(resolve => setTimeout(resolve, 2000))
}
}

//
//
// All interaction with the PFI happens in the context of an exchange.
// This is where for example a quote would show up in result to an RFQ:
const exchange = await TbdexHttpClient.getExchange({
pfiDid: pfiDid,
did: alice,
exchangeId: rfq.exchangeId
})

console.log('got exchange:', JSON.stringify(exchange, null, 2))

// Place an order against that quote:
const order = Order.create({
metadata: {
from: alice.uri,
to: pfiDid,
exchangeId: quote.exchangeId
},
})
await order.sign(alice)
await TbdexHttpClient.submitOrder(order)
console.log('Sent order: ', JSON.stringify(order, null, 2))

await pollForStatus(order, pfiDid, alice)



/*
* This is a very simple polling function that will poll for the status of an order.
*/
async function pollForStatus(order: Order, pfiDid: string, did: BearerDid) {
let close: Close
while (!close) {
const exchange = await TbdexHttpClient.getExchange({
pfiDid: pfiDid,
did: did,
exchangeId: order.exchangeId
})

for (const message of exchange) {
if (message instanceof OrderStatus) {
console.log('we got a new order status')
const orderStatus = message as OrderStatus
console.log('orderStatus', JSON.stringify(orderStatus, null, 2))
} else if (message instanceof Close) {
console.log('we have a close message')
close = message as Close
console.log('close', JSON.stringify(close, null, 2))
return close
}
}
}
}

const [end_balances] = await TbdexHttpClient.getBalances({ pfiDid: pfiDid, did: alice })
console.log('starting balances:', JSON.stringify(balances, null, 2))
console.log('end balances:', JSON.stringify(end_balances, null, 2))

0 comments on commit 4424068

Please sign in to comment.