-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2762 from build-5/develop
Merge bulk buy
- Loading branch information
Showing
46 changed files
with
1,861 additions
and
178 deletions.
There are no files selected for viewing
196 changes: 135 additions & 61 deletions
196
.github/workflows/functions_tangle-online-unit-tests_emulator.yml
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
19 changes: 19 additions & 0 deletions
19
packages/functions/src/controls/nft/NftPurchaseBulkRequestSchema.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { MAX_NFT_BULK_PURCHASE, NftPurchaseBulkRequest } from '@build-5/interfaces'; | ||
import Joi from 'joi'; | ||
import { toJoiObject } from '../../services/joi/common'; | ||
import { nftPurchaseSchema } from './NftPurchaseRequestSchema'; | ||
|
||
export const nftPurchaseBulkSchema = toJoiObject<NftPurchaseBulkRequest>({ | ||
orders: Joi.array() | ||
.items(nftPurchaseSchema) | ||
.min(1) | ||
.max(MAX_NFT_BULK_PURCHASE) | ||
.description( | ||
`List of collections&nfts to purchase, minimum 1, maximum ${MAX_NFT_BULK_PURCHASE}`, | ||
) | ||
.required(), | ||
}) | ||
.description('Request object to create an NFT bulk purchase order') | ||
.meta({ | ||
className: 'NftPurchaseBulkRequest', | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
packages/functions/src/controls/nft/nft.puchase.bulk.control.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { build5Db } from '@build-5/database'; | ||
import { COL, NftPurchaseBulkRequest, Transaction } from '@build-5/interfaces'; | ||
import { createNftBulkOrder } from '../../services/payment/tangle-service/nft/nft-purchase.bulk.service'; | ||
import { Context } from '../common'; | ||
|
||
export const orderNftBulkControl = async ({ | ||
ip, | ||
owner, | ||
params, | ||
project, | ||
}: Context<NftPurchaseBulkRequest>): Promise<Transaction> => { | ||
const order = await createNftBulkOrder(project, params.orders, owner, ip); | ||
const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${order.uid}`); | ||
await orderDocRef.create(order); | ||
|
||
return (await orderDocRef.get<Transaction>())!; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
189 changes: 189 additions & 0 deletions
189
packages/functions/src/services/payment/nft/nft-purchase.bulk.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
import { build5Db } from '@build-5/database'; | ||
import { | ||
COL, | ||
Collection, | ||
Entity, | ||
Nft, | ||
NftBulkOrder, | ||
SUB_COL, | ||
Space, | ||
TRANSACTION_AUTO_EXPIRY_MS, | ||
Transaction, | ||
TransactionPayloadType, | ||
TransactionType, | ||
TransactionValidationType, | ||
getMilestoneCol, | ||
} from '@build-5/interfaces'; | ||
import dayjs from 'dayjs'; | ||
import { get } from 'lodash'; | ||
import { getAddress } from '../../../utils/address.utils'; | ||
import { getRestrictions } from '../../../utils/common.utils'; | ||
import { dateToTimestamp } from '../../../utils/dateTime.utils'; | ||
import { getSpace } from '../../../utils/space.utils'; | ||
import { getRandomEthAddress } from '../../../utils/wallet.utils'; | ||
import { WalletService } from '../../wallet/wallet.service'; | ||
import { BaseService, HandlerParams } from '../base'; | ||
import { assertNftCanBePurchased, getMember } from '../tangle-service/nft/nft-purchase.service'; | ||
import { NftPurchaseService } from './nft-purchase.service'; | ||
|
||
export class NftPurchaseBulkService extends BaseService { | ||
public handleRequest = async ({ order, match, tranEntry, tran, project }: HandlerParams) => { | ||
const payment = await this.transactionService.createPayment(order, match); | ||
|
||
const promises = (order.payload.nftOrders || []).map((nftOrder) => | ||
this.createNftPurchaseOrder(project, order, nftOrder), | ||
); | ||
const nftOrders = await Promise.all(promises); | ||
|
||
const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${order.uid}`); | ||
this.transactionService.push({ | ||
ref: orderDocRef, | ||
data: { | ||
'payload.nftOrders': nftOrders, | ||
'payload.reconciled': true, | ||
'payload.chainReference': match.msgId, | ||
}, | ||
action: 'update', | ||
}); | ||
|
||
const total = nftOrders.reduce((acc, act) => acc + act.price, 0); | ||
if (total < tranEntry.amount) { | ||
const credit = { | ||
project, | ||
type: TransactionType.CREDIT, | ||
uid: getRandomEthAddress(), | ||
space: order.space, | ||
member: order.member || match.from, | ||
network: order.network, | ||
payload: { | ||
type: TransactionPayloadType.NFT_PURCHASE_BULK, | ||
amount: tranEntry.amount - total, | ||
sourceAddress: order.payload.targetAddress, | ||
targetAddress: match.from, | ||
sourceTransaction: [payment.uid], | ||
reconciled: false, | ||
void: false, | ||
}, | ||
}; | ||
const docRef = build5Db().doc(`${COL.TRANSACTION}/${credit.uid}`); | ||
this.transactionService.push({ ref: docRef, data: credit, action: 'set' }); | ||
} | ||
|
||
if (total) { | ||
const targetAddresses = nftOrders | ||
.filter((o) => o.price > 0) | ||
.map((o) => ({ toAddress: o.targetAddress!, amount: o.price })); | ||
const transfer: Transaction = { | ||
project, | ||
type: TransactionType.UNLOCK, | ||
uid: getRandomEthAddress(), | ||
space: order.space || '', | ||
member: order.member || match.from, | ||
network: order.network, | ||
payload: { | ||
type: TransactionPayloadType.TANGLE_TRANSFER_MANY, | ||
amount: total, | ||
sourceAddress: order.payload.targetAddress, | ||
targetAddresses, | ||
sourceTransaction: [payment.uid], | ||
expiresOn: dateToTimestamp(dayjs().add(TRANSACTION_AUTO_EXPIRY_MS)), | ||
milestoneTransactionPath: `${getMilestoneCol(order.network!)}/${tran.milestone}/${ | ||
SUB_COL.TRANSACTIONS | ||
}/${tran.uid}`, | ||
}, | ||
}; | ||
const docRef = build5Db().doc(`${COL.TRANSACTION}/${transfer.uid}`); | ||
this.transactionService.push({ ref: docRef, data: transfer, action: 'set' }); | ||
} | ||
}; | ||
|
||
private createNftPurchaseOrder = async ( | ||
project: string, | ||
order: Transaction, | ||
nftOrder: NftBulkOrder, | ||
) => { | ||
if (!nftOrder.price) { | ||
return { ...nftOrder, targetAddress: '' }; | ||
} | ||
|
||
const nftDocRef = build5Db().doc(`${COL.NFT}/${nftOrder.nft}`); | ||
const nft = <Nft>await this.transactionService.get(nftDocRef); | ||
|
||
const collectionDocRef = build5Db().doc(`${COL.COLLECTION}/${nft.collection}`); | ||
const collection = <Collection>await collectionDocRef.get(); | ||
|
||
const spaceDocRef = build5Db().doc(`${COL.SPACE}/${nft.space}`); | ||
const space = <Space>await spaceDocRef.get(); | ||
try { | ||
await assertNftCanBePurchased( | ||
space, | ||
collection, | ||
nft, | ||
nftOrder.requestedNft, | ||
order.member!, | ||
true, | ||
); | ||
|
||
if (nft.auction) { | ||
const service = new NftPurchaseService(this.transactionService); | ||
await service.creditBids(nft.auction); | ||
} | ||
|
||
const wallet = await WalletService.newWallet(order.network); | ||
const targetAddress = await wallet.getNewIotaAddressDetails(); | ||
|
||
const royaltySpace = await getSpace(collection.royaltiesSpace); | ||
|
||
const nftPurchaseOrderId = getRandomEthAddress(); | ||
|
||
const nftDocRef = build5Db().doc(`${COL.NFT}/${nft.uid}`); | ||
this.transactionService.push({ | ||
ref: nftDocRef, | ||
data: { locked: true, lockedBy: order.uid }, | ||
action: 'update', | ||
}); | ||
|
||
const currentOwner = nft.owner ? await getMember(nft.owner) : space; | ||
|
||
const nftPurchaseOrder = { | ||
project, | ||
type: TransactionType.ORDER, | ||
uid: nftPurchaseOrderId, | ||
member: order.member!, | ||
space: space.uid, | ||
network: order.network, | ||
payload: { | ||
type: TransactionPayloadType.NFT_PURCHASE, | ||
amount: nftOrder.price, | ||
targetAddress: targetAddress.bech32, | ||
beneficiary: nft.owner ? Entity.MEMBER : Entity.SPACE, | ||
beneficiaryUid: nft.owner || collection.space, | ||
beneficiaryAddress: getAddress(currentOwner, order.network), | ||
royaltiesFee: collection.royaltiesFee, | ||
royaltiesSpace: collection.royaltiesSpace || '', | ||
royaltiesSpaceAddress: getAddress(royaltySpace, order.network), | ||
expiresOn: dateToTimestamp(dayjs().add(TRANSACTION_AUTO_EXPIRY_MS)), | ||
validationType: TransactionValidationType.ADDRESS_AND_AMOUNT, | ||
reconciled: false, | ||
void: false, | ||
chainReference: null, | ||
nft: nft.uid, | ||
collection: collection.uid, | ||
restrictions: getRestrictions(collection, nft), | ||
}, | ||
linkedTransactions: [], | ||
}; | ||
const docRef = build5Db().doc(`${COL.TRANSACTION}/${nftPurchaseOrder.uid}`); | ||
this.transactionService.push({ ref: docRef, data: nftPurchaseOrder, action: 'set' }); | ||
|
||
return { ...nftOrder, targetAddress: targetAddress.bech32 }; | ||
} catch (error) { | ||
return { | ||
...nftOrder, | ||
price: 0, | ||
error: get(error, 'details.code', 0), | ||
targetAddress: '', | ||
} as NftBulkOrder; | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
...s/functions/src/services/payment/tangle-service/nft/NftPurchaseBulkTangleRequestSchema.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { | ||
MAX_NFT_BULK_PURCHASE, | ||
NftPurchaseBulkTangleRequest, | ||
TangleRequestType, | ||
} from '@build-5/interfaces'; | ||
import Joi from 'joi'; | ||
import { CommonJoi, toJoiObject } from '../../../joi/common'; | ||
import { baseTangleSchema } from '../common'; | ||
|
||
const nftPurchaseSchema = Joi.object({ | ||
collection: CommonJoi.uid().description( | ||
'Build5 id of the collection in case a random nft is bought.', | ||
), | ||
nft: CommonJoi.uid(false).description('Build5 id of the nft to be purchased.'), | ||
}); | ||
|
||
export const nftPurchaseBulkSchema = toJoiObject<NftPurchaseBulkTangleRequest>({ | ||
...baseTangleSchema(TangleRequestType.NFT_PURCHASE_BULK), | ||
orders: Joi.array() | ||
.items(nftPurchaseSchema) | ||
.min(1) | ||
.max(MAX_NFT_BULK_PURCHASE) | ||
.description( | ||
`List of collections&nfts to purchase, minimum 1, maximum ${MAX_NFT_BULK_PURCHASE}`, | ||
) | ||
.required(), | ||
disableWithdraw: Joi.boolean().description( | ||
"If set to true, NFT will not be sent to the buyer's validated address upon purchase.", | ||
), | ||
}) | ||
.description('Tangle request object to create an NFT bulk purchase order') | ||
.meta({ | ||
className: 'NftPurchaseBulkTangleRequest', | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.