Skip to content

Commit

Permalink
Recent small spec changes (TBD54566975#213)
Browse files Browse the repository at this point in the history
* Address tbdex TBD54566975#260

* Add name, group, and description to PaymentMethod

* Add min and max to PaymentMethod

* Add estimatedSettlementTime

* Update tbdex to latest main and move compiled-validators to cjs

* audit fix

* Update rfq.ts

Co-authored-by: nitro-neal <[email protected]>

* add seconds comment

* Replace require with import in compiled validators

---------

Co-authored-by: nitro-neal <[email protected]>
  • Loading branch information
diehuxx and nitro-neal authored Mar 27, 2024
1 parent 529b37a commit 02ce905
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 198 deletions.
4 changes: 2 additions & 2 deletions packages/http-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@tbdex/protocol": "workspace:*",
"@web5/dids": "0.4.1",
"cors": "2.8.5",
"express": "4.18.2"
"express": "4.19.2"
},
"devDependencies": {
"@types/chai": "4.3.6",
Expand All @@ -52,4 +52,4 @@
"keywords": [
"tbdex"
]
}
}
8 changes: 4 additions & 4 deletions packages/http-server/src/in-memory-offerings-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ export class InMemoryOfferingsApi implements OfferingsApi {
return allOfferings.filter((offering) => {
// If filter includes a field, make sure the returned offerings match
return (!id || id === offering.metadata.id) &&
(!payinCurrency || payinCurrency === offering.data.payinCurrency.currencyCode) &&
(!payoutCurrency || payoutCurrency === offering.data.payoutCurrency.currencyCode) &&
(!payinMethodKind || offering.data.payinMethods.map(pm => pm.kind).includes(payinMethodKind)) &&
(!payoutMethodKind || offering.data.payoutMethods.map(pm => pm.kind).includes(payoutMethodKind))
(!payinCurrency || payinCurrency === offering.data.payin.currencyCode) &&
(!payoutCurrency || payoutCurrency === offering.data.payout.currencyCode) &&
(!payinMethodKind || offering.data.payin.methods.map(pm => pm.kind).includes(payinMethodKind)) &&
(!payoutMethodKind || offering.data.payout.methods.map(pm => pm.kind).includes(payoutMethodKind))
})
}

Expand Down
85 changes: 43 additions & 42 deletions packages/http-server/tests/create-exchange.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,44 +238,45 @@ describe('POST /exchanges/:exchangeId/rfq', () => {
data: {
...DevTools.createOfferingData(),
requiredClaims : undefined,
payinCurrency : {
payin : {
currencyCode : 'BTC',
minAmount : '1000.0'
min : '1000.0',
methods : [{
kind : 'BTC_ADDRESS',
requiredPaymentDetails : {
$schema : 'http://json-schema.org/draft-07/schema',
type : 'object',
properties : {
btcAddress: {
type : 'string',
description : 'your Bitcoin wallet address'
}
},
required : ['btcAddress'],
additionalProperties : false
}
}]
},
payoutCurrency: {
payout: {
currencyCode : 'BTC',
minAmount : '1000.0',
},
payinMethods: [{
kind : 'BTC_ADDRESS',
requiredPaymentDetails : {
$schema : 'http://json-schema.org/draft-07/schema',
type : 'object',
properties : {
btcAddress: {
type : 'string',
description : 'your Bitcoin wallet address'
}
},
required : ['btcAddress'],
additionalProperties : false
}
}],
payoutMethods: [{
kind : 'BTC_ADDRESS',
requiredPaymentDetails : {
$schema : 'http://json-schema.org/draft-07/schema',
type : 'object',
properties : {
btcAddress: {
type : 'string',
description : 'your Bitcoin wallet address'
}
min : '1000.0',
methods : [{
kind : 'BTC_ADDRESS',
requiredPaymentDetails : {
$schema : 'http://json-schema.org/draft-07/schema',
type : 'object',
properties : {
btcAddress: {
type : 'string',
description : 'your Bitcoin wallet address'
}
},
required : ['btcAddress'],
additionalProperties : false
},
required : ['btcAddress'],
additionalProperties : false
}
}]
estimatedSettlementTime: 10, // seconds
}]
},
},
})
await offering.sign(pfiDid);
Expand All @@ -289,17 +290,17 @@ describe('POST /exchanges/:exchangeId/rfq', () => {
},
data: {
...DevTools.createRfqData(),
offeringId : offering.metadata.id,
claims : [],
payinAmount : offering.data.payinCurrency.minAmount!,
payinMethod : {
kind : offering.data.payinMethods[0].kind,
offeringId : offering.metadata.id,
claims : [],
payin : {
kind : offering.data.payin.methods[0].kind,
paymentDetails : {
btcAddress: '1234',
}
},
amount: offering.data.payin.min!,
},
payoutMethod: {
kind : offering.data.payoutMethods[0].kind,
payout: {
kind : offering.data.payout.methods[0].kind,
paymentDetails : {
btcAddress: '1234',
}
Expand Down
8 changes: 4 additions & 4 deletions packages/http-server/tests/get-offerings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ describe('GET /offerings', () => {
// Specify query params
const queryParams: GetOfferingsFilter = {
id : offering.metadata.id,
payinCurrency : offering.data.payinCurrency.currencyCode,
payoutCurrency : offering.data.payoutCurrency.currencyCode,
payinMethodKind : offering.data.payinMethods[0].kind,
payoutMethodKind : offering.data.payoutMethods[0].kind
payinCurrency : offering.data.payin.currencyCode,
payoutCurrency : offering.data.payout.currencyCode,
payinMethodKind : offering.data.payin.methods[0].kind,
payoutMethodKind : offering.data.payout.methods[0].kind
}
const queryParamsString: string =
Object.entries(queryParams)
Expand Down
20 changes: 19 additions & 1 deletion packages/protocol/build/compile-validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,25 @@ for (const schemaName in schemas) {
validator.addSchema(schemas[schemaName], schemaName)
}

const moduleCode = standaloneCode(validator)
const generatedCode = standaloneCode(validator)

// https://github.com/ajv-validator/ajv/issues/2209
// ESM generation is broken in AJV standalone.
// In particular, it will "require" files from AJVs runtime directory instead of "import"ing.
function replaceRequireWithImport(inputString) {
const variableNameRegex = /\w+/; // Matches the variable name
const moduleNameRegex = /[^"']+/; // Matches the module name
const regex = new RegExp(
`const\\s+(${variableNameRegex.source})\\s*=\\s*require\\s*\\(\\s*[\"'](${moduleNameRegex.source})[\"']\\s*\\)\\.default`,
'g'
);

const replacedString = inputString.replace(regex, 'import { default as $1 } from "$2.js"');
return replacedString;
}
const moduleCode = replaceRequireWithImport(generatedCode)


const __dirname = url.fileURLToPath(new URL('.', import.meta.url))

await mkdirp(path.join(__dirname, '../generated'))
Expand Down
119 changes: 60 additions & 59 deletions packages/protocol/src/dev-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,65 +43,66 @@ export class DevTools {
*/
static createOfferingData(): OfferingData {
return {
description : 'Selling BTC for USD',
payinCurrency : {
description : 'Selling BTC for USD',
payin : {
currencyCode : 'USD',
minAmount : '0.0',
maxAmount : '999999.99',
min : '0.0',
max : '999999.99',
methods : [{
kind : 'DEBIT_CARD',
requiredPaymentDetails : {
$schema : 'http://json-schema.org/draft-07/schema',
type : 'object',
properties : {
cardNumber: {
type : 'string',
description : 'The 16-digit debit card number',
minLength : 16,
maxLength : 16
},
expiryDate: {
type : 'string',
description : 'The expiry date of the card in MM/YY format',
pattern : '^(0[1-9]|1[0-2])\\/([0-9]{2})$'
},
cardHolderName: {
type : 'string',
description : 'Name of the cardholder as it appears on the card'
},
cvv: {
type : 'string',
description : 'The 3-digit CVV code',
minLength : 3,
maxLength : 3
}
},
required : ['cardNumber', 'expiryDate', 'cardHolderName', 'cvv'],
additionalProperties : false
}
}]
},
payoutCurrency: {
payout: {
currencyCode : 'BTC',
maxAmount : '999526.11'
},
payoutUnitsPerPayinUnit : '0.00003826',
payinMethods : [{
kind : 'DEBIT_CARD',
requiredPaymentDetails : {
$schema : 'http://json-schema.org/draft-07/schema',
type : 'object',
properties : {
cardNumber: {
type : 'string',
description : 'The 16-digit debit card number',
minLength : 16,
maxLength : 16
},
expiryDate: {
type : 'string',
description : 'The expiry date of the card in MM/YY format',
pattern : '^(0[1-9]|1[0-2])\\/([0-9]{2})$'
},
cardHolderName: {
type : 'string',
description : 'Name of the cardholder as it appears on the card'
max : '999526.11',
methods : [{
kind : 'BTC_ADDRESS',
requiredPaymentDetails : {
$schema : 'http://json-schema.org/draft-07/schema',
type : 'object',
properties : {
btcAddress: {
type : 'string',
description : 'your Bitcoin wallet address'
}
},
cvv: {
type : 'string',
description : 'The 3-digit CVV code',
minLength : 3,
maxLength : 3
}
},
required : ['cardNumber', 'expiryDate', 'cardHolderName', 'cvv'],
additionalProperties : false
}
}],
payoutMethods: [{
kind : 'BTC_ADDRESS',
requiredPaymentDetails : {
$schema : 'http://json-schema.org/draft-07/schema',
type : 'object',
properties : {
btcAddress: {
type : 'string',
description : 'your Bitcoin wallet address'
}
required : ['btcAddress'],
additionalProperties : false
},
required : ['btcAddress'],
additionalProperties : false
}
}],
requiredClaims: {
estimatedSettlementTime: 10, // seconds
}]
},
payoutUnitsPerPayinUnit : '0.00003826',
requiredClaims : {
id : '7ce4004c-3c38-4853-968b-e411bafcd945',
input_descriptors : [{
id : 'bbdb9b7c-5754-4f46-b63b-590bada959e0',
Expand Down Expand Up @@ -182,24 +183,24 @@ export class DevTools {
}

return {
offeringId : Resource.generateId('offering'),
payinMethod : {
offeringId : Resource.generateId('offering'),
payin : {
kind : 'DEBIT_CARD',
amount : '200.00',
paymentDetails : {
'cardNumber' : '1234567890123456',
'expiryDate' : '12/22',
'cardHolderName' : 'Ephraim Bartholomew Winthrop',
'cvv' : '123'
}
},
payoutMethod: {
payout: {
kind : 'BTC_ADDRESS',
paymentDetails : {
btcAddress: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'
}
},
payinAmount : '200.00',
claims : [vcJwt]
claims: [vcJwt]
}
}
}
28 changes: 14 additions & 14 deletions packages/protocol/src/message-kinds/rfq.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MessageKind, MessageModel, PaymentMethod, RfqData, RfqMetadata, SelectedPaymentMethod } from '../types.js'
import type { MessageKind, MessageModel, PayinMethod, RfqData, RfqMetadata, SelectedPayoutMethod } from '../types.js'

import { BigNumber } from 'bignumber.js'
import { Offering } from '../resource-kinds/index.js'
Expand Down Expand Up @@ -104,30 +104,30 @@ export class Rfq extends Message {
throw new Error(`offering id mismatch. (rfq) ${this.data.offeringId} !== ${offering.metadata.id} (offering)`)
}

// Verifyin payin amount is less than maximum
// Verifying payin amount is less than maximum
let payinAmount: BigNumber
if (offering.data.payinCurrency.maxAmount) {
payinAmount = BigNumber(this.data.payinAmount)
const maxAmount = BigNumber(offering.data.payinCurrency.maxAmount)
if (offering.data.payin.max) {
payinAmount = BigNumber(this.data.payin.amount)
const maxAmount = BigNumber(offering.data.payin.max)

if (payinAmount.isGreaterThan(maxAmount)) {
throw new Error(`rfq payinAmount exceeds offering's maxAmount. (rfq) ${this.data.payinAmount} > ${offering.data.payinCurrency.maxAmount} (offering)`)
throw new Error(`rfq payinAmount exceeds offering's maxAmount. (rfq) ${this.data.payin.amount} > ${offering.data.payin.max} (offering)`)
}
}

// Verify payin amount is more than minimum
if (offering.data.payinCurrency.minAmount) {
payinAmount ??= BigNumber(this.data.payinAmount)
const minAmount = BigNumber(offering.data.payinCurrency.minAmount)
if (offering.data.payin.min) {
payinAmount ??= BigNumber(this.data.payin.amount)
const minAmount = BigNumber(offering.data.payin.min)

if (payinAmount.isLessThan(minAmount)) {
throw new Error(`rfq payinAmount is below offering's minAmount. (rfq) ${this.data.payinAmount} > ${offering.data.payinCurrency.minAmount} (offering)`)
throw new Error(`rfq payinAmount is below offering's minAmount. (rfq) ${this.data.payin.amount} > ${offering.data.payin.min} (offering)`)
}
}

// Verify payin/payout methods
this.verifyPaymentMethod(this.data.payinMethod, offering.data.payinMethods, 'payin')
this.verifyPaymentMethod(this.data.payoutMethod, offering.data.payoutMethods, 'payout')
this.verifyPaymentMethod(this.data.payin, offering.data.payin.methods, 'payin')
this.verifyPaymentMethod(this.data.payout, offering.data.payout.methods, 'payout')

await this.verifyClaims(offering)
}
Expand All @@ -143,8 +143,8 @@ export class Rfq extends Message {
* @throws if rfqPaymentMethod property `paymentDetails` cannot be validated against the provided offering's paymentMethod's requiredPaymentDetails
*/
private verifyPaymentMethod(
rfqPaymentMethod: SelectedPaymentMethod,
allowedPaymentMethods: PaymentMethod[],
rfqPaymentMethod: SelectedPayoutMethod,
allowedPaymentMethods: PayinMethod[],
payDirection: 'payin' | 'payout'
): void {
const paymentMethodMatches = allowedPaymentMethods.filter(paymentMethod => paymentMethod.kind === rfqPaymentMethod.kind)
Expand Down
Loading

0 comments on commit 02ce905

Please sign in to comment.