diff --git a/.github/workflows/functions_emulated-tests.yml b/.github/workflows/functions_emulated-tests.yml index 567fe3b494..386fad4ead 100644 --- a/.github/workflows/functions_emulated-tests.yml +++ b/.github/workflows/functions_emulated-tests.yml @@ -231,3 +231,39 @@ jobs: name: firestore-data-test-chunk_4 path: ./packages/functions/firestore-data/ retention-days: 1 + chunk_5: + needs: npm-install + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16.x + - uses: actions/cache@v3 + with: + path: | + node_modules + packages/functions/node_modules + packages/interfaces/node_modules + key: ${{ runner.os }}-modules-${{ hashFiles('**/package.json') }} + - name: Init + run: | + npm run build:functions + npm install -g firebase-tools + - name: Test + working-directory: packages/functions + run: | + export GOOGLE_APPLICATION_CREDENTIALS="./test-service-account-key.json" + npm run milestone-sync & + firebase emulators:exec " + npm run test:ci -- --findRelatedTests test/controls/project/project.deactivate.spec.ts && + npm run test:ci -- --findRelatedTests test/controls/project/project.create.spec.ts + " --project dev --only functions,firestore,storage,ui,auth --export-on-exit=./firestore-data + - name: Archive firestore data + uses: actions/upload-artifact@v3 + if: ${{ failure() }} + with: + name: firestore-data-test-chunk_5 + path: ./packages/functions/firestore-data/ + retention-days: 1 diff --git a/.github/workflows/functions_online-emulated-tests.yml b/.github/workflows/functions_online-emulated-tests.yml index f2c2b5d54d..6faa02d88a 100644 --- a/.github/workflows/functions_online-emulated-tests.yml +++ b/.github/workflows/functions_online-emulated-tests.yml @@ -223,7 +223,8 @@ jobs: npm run test-online:ci -- --findRelatedTests test/controls/token/token.create.spec.ts && npm run test-online:ci -- --findRelatedTests test/controls/token/token.cancel.pub.sale.spec.ts && npm run test-online:ci -- --findRelatedTests test/controls/token/token.airdrop.spec.ts && - npm run test-online:ci -- --findRelatedTests test/controls/token/token.airdrop.claim.spec.ts + npm run test-online:ci -- --findRelatedTests test/controls/token/token.airdrop.claim.spec.ts && + npm run test-online:ci -- --findRelatedTests test/controls/project/project.deactivate.spec.ts " --project dev --only functions,firestore,storage,ui,auth --export-on-exit=./firestore-data - name: Archive firestore data uses: actions/upload-artifact@v3 @@ -232,3 +233,38 @@ jobs: name: firestore-data-test-online-chunk_4 path: ./packages/functions/firestore-data/ retention-days: 1 + chunk_5: + needs: npm-install + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16.x + - uses: actions/cache@v3 + with: + path: | + node_modules + packages/functions/node_modules + packages/interfaces/node_modules + key: ${{ runner.os }}-modules-${{ hashFiles('**/package.json') }} + - name: Init + run: | + npm run build:functions + npm install -g firebase-tools + - name: Test + working-directory: packages/functions + run: | + export GOOGLE_APPLICATION_CREDENTIALS="./test-service-account-key.json" + npm run milestone-sync & + firebase emulators:exec " + npm run test-online:ci -- --findRelatedTests test/controls/project/project.create.spec.ts + " --project dev --only functions,firestore,storage,ui,auth --export-on-exit=./firestore-data + - name: Archive firestore data + uses: actions/upload-artifact@v3 + if: ${{ failure() }} + with: + name: firestore-data-test-online-chunk_5 + path: ./packages/functions/firestore-data/ + retention-days: 1 diff --git a/packages/database/src/firestore/firestore.ts b/packages/database/src/firestore/firestore.ts index 04a935c99b..a965374585 100644 --- a/packages/database/src/firestore/firestore.ts +++ b/packages/database/src/firestore/firestore.ts @@ -44,6 +44,11 @@ export class Firestore implements IDatabase { public arrayRemove = (...value: T[]) => admin.firestore.FieldValue.arrayRemove(...value); public deleteField = () => admin.firestore.FieldValue.delete(); + + public get = async (col: COL, uid: string) => { + const docRef = this.db.doc(`${col}/${uid}`); + return (await docRef.get()).data() as T; + }; } export class FirestoreBatch implements IBatch { diff --git a/packages/database/src/firestore/interfaces.ts b/packages/database/src/firestore/interfaces.ts index 41189e1cbb..094594c3df 100644 --- a/packages/database/src/firestore/interfaces.ts +++ b/packages/database/src/firestore/interfaces.ts @@ -13,6 +13,8 @@ export interface IDatabase { arrayUnion: (...value: T[]) => any; arrayRemove: (...value: T[]) => any; deleteField: () => any; + + get: (col: COL, uid: string) => Promise; } export interface ICollectionGroup { diff --git a/packages/functions/src/controls/address.control.ts b/packages/functions/src/controls/address.control.ts index 83dd5a771d..1d4866b3d9 100644 --- a/packages/functions/src/controls/address.control.ts +++ b/packages/functions/src/controls/address.control.ts @@ -7,10 +7,14 @@ import { Network, WenError, } from '@build-5/interfaces'; +import { Context } from '../runtime/firebase/common'; import { createAddressValidationOrder } from '../services/payment/tangle-service/address/address-validation.service'; import { invalidArgument } from '../utils/error.utils'; -export const validateAddressControl = async (owner: string, params: AddressValidationRequest) => { +export const validateAddressControl = async ( + { owner }: Context, + params: AddressValidationRequest, +) => { const network = (params.network as Network) || DEFAULT_NETWORK; const member = await build5Db().doc(`${COL.MEMBER}/${owner}`).get(); diff --git a/packages/functions/src/controls/auth.control.ts b/packages/functions/src/controls/auth.control.ts index 0e74030746..1169b520ba 100644 --- a/packages/functions/src/controls/auth.control.ts +++ b/packages/functions/src/controls/auth.control.ts @@ -1,9 +1,10 @@ import { TOKEN_EXPIRY_HOURS } from '@build-5/interfaces'; import dayjs from 'dayjs'; import jwt from 'jsonwebtoken'; +import { Context } from '../runtime/firebase/common'; import { getJwtSecretKey } from '../utils/config.utils'; -export const generateCustomTokenControl = async (owner: string) => { +export const generateCustomTokenControl = async ({ owner }: Context) => { const rawJwt = { uid: owner, iat: dayjs().unix(), diff --git a/packages/functions/src/controls/award/award.approve.participant.ts b/packages/functions/src/controls/award/award.approve.participant.ts index 1b93757045..28e5c69472 100644 --- a/packages/functions/src/controls/award/award.approve.participant.ts +++ b/packages/functions/src/controls/award/award.approve.participant.ts @@ -6,10 +6,11 @@ import { Transaction, } from '@build-5/interfaces'; import { get } from 'lodash'; +import { Context } from '../../runtime/firebase/common'; import { approveAwardParticipant } from '../../services/payment/tangle-service/award/award.approve.participant.service'; export const approveAwardParticipantControl = async ( - owner: string, + { owner }: Context, params: AwardApproveParticipantRequest, ): Promise => { const members = params.members.map((m) => m.toLowerCase()); diff --git a/packages/functions/src/controls/award/award.cancel.ts b/packages/functions/src/controls/award/award.cancel.ts index d0adeece59..42784dc70b 100644 --- a/packages/functions/src/controls/award/award.cancel.ts +++ b/packages/functions/src/controls/award/award.cancel.ts @@ -1,10 +1,14 @@ import { build5Db } from '@build-5/database'; import { Award, AwardCancelRequest, COL, WenError } from '@build-5/interfaces'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian } from '../../utils/token.utils'; -export const cancelAwardControl = (owner: string, params: AwardCancelRequest): Promise => +export const cancelAwardControl = ( + { owner }: Context, + params: AwardCancelRequest, +): Promise => build5Db().runTransaction(async (transaction) => { const awardDocRef = build5Db().doc(`${COL.AWARD}/${params.uid}`); const award = await transaction.get(awardDocRef); diff --git a/packages/functions/src/controls/award/award.create.ts b/packages/functions/src/controls/award/award.create.ts index f885bf1f7d..b5aa86f312 100644 --- a/packages/functions/src/controls/award/award.create.ts +++ b/packages/functions/src/controls/award/award.create.ts @@ -1,8 +1,9 @@ import { build5Db } from '@build-5/database'; import { Award, AwardCreateRequest, COL, SUB_COL } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { createAward } from '../../services/payment/tangle-service/award/award.create.service'; -export const createAwardControl = async (owner: string, params: AwardCreateRequest) => { +export const createAwardControl = async ({ owner }: Context, params: AwardCreateRequest) => { const { owner: awardOwner, award } = await createAward(owner, params); const batch = build5Db().batch(); diff --git a/packages/functions/src/controls/award/award.fund.ts b/packages/functions/src/controls/award/award.fund.ts index 88620aab28..736ca1803b 100644 --- a/packages/functions/src/controls/award/award.fund.ts +++ b/packages/functions/src/controls/award/award.fund.ts @@ -1,12 +1,13 @@ import { build5Db } from '@build-5/database'; import { AwardFundRequest, COL, Transaction } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { createAwardFundOrder, getAwardForFunding, } from '../../services/payment/tangle-service/award/award.fund.service'; export const fundAwardControl = async ( - owner: string, + { owner }: Context, params: AwardFundRequest, ): Promise => { const award = await getAwardForFunding(owner, params.uid); diff --git a/packages/functions/src/controls/award/award.owner.ts b/packages/functions/src/controls/award/award.owner.ts index 119d7c9d58..dd6fe57c87 100644 --- a/packages/functions/src/controls/award/award.owner.ts +++ b/packages/functions/src/controls/award/award.owner.ts @@ -1,4 +1,3 @@ -import { build5Db } from '@build-5/database'; import { Award, AwardAddOwnerRequest, @@ -8,11 +7,13 @@ import { WenError, } from '@build-5/interfaces'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; +import { build5Db } from '@build-5/database'; export const addOwnerControl = async ( - owner: string, + { owner }: Context, params: AwardAddOwnerRequest, ): Promise => { const awardDocRef = build5Db().doc(`${COL.AWARD}/${params.uid}`); diff --git a/packages/functions/src/controls/award/award.participate.ts b/packages/functions/src/controls/award/award.participate.ts index f30eac9ddb..75319ad703 100644 --- a/packages/functions/src/controls/award/award.participate.ts +++ b/packages/functions/src/controls/award/award.participate.ts @@ -8,11 +8,12 @@ import { WenError, } from '@build-5/interfaces'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; export const awardParticipateControl = async ( - owner: string, + { owner }: Context, params: AwardParticpateRequest, ): Promise => { const awardDocRef = build5Db().doc(`${COL.AWARD}/${params.uid}`); diff --git a/packages/functions/src/controls/award/award.reject.ts b/packages/functions/src/controls/award/award.reject.ts index 95089e61a0..55c6e03133 100644 --- a/packages/functions/src/controls/award/award.reject.ts +++ b/packages/functions/src/controls/award/award.reject.ts @@ -1,10 +1,11 @@ import { build5Db } from '@build-5/database'; import { Award, AwardRejectRequest, COL, WenError } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian } from '../../utils/token.utils'; export const rejectAwardControl = async ( - owner: string, + { owner }: Context, params: AwardRejectRequest, ): Promise => { const awardDocRef = build5Db().doc(`${COL.AWARD}/${params.uid}`); diff --git a/packages/functions/src/controls/collection/collection-mint.control.ts b/packages/functions/src/controls/collection/collection-mint.control.ts index b6d75486b8..dcbdd1abb5 100644 --- a/packages/functions/src/controls/collection/collection-mint.control.ts +++ b/packages/functions/src/controls/collection/collection-mint.control.ts @@ -20,6 +20,7 @@ import { import { AddressTypes, ED25519_ADDRESS_TYPE, INodeInfo } from '@iota/iota.js-next'; import dayjs from 'dayjs'; import { last } from 'lodash'; +import { Context } from '../../runtime/firebase/common'; import { SmrWallet } from '../../services/wallet/SmrWalletService'; import { AddressDetails, WalletService } from '../../services/wallet/wallet'; import { assertMemberHasValidAddress, assertSpaceHasValidAddress } from '../../utils/address.utils'; @@ -36,7 +37,10 @@ import { createAliasOutput } from '../../utils/token-minting-utils/alias.utils'; import { assertIsGuardian } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; -export const mintCollectionOrderControl = async (owner: string, params: CollectionMintRequest) => { +export const mintCollectionOrderControl = async ( + { owner }: Context, + params: CollectionMintRequest, +) => { const network = params.network as Network; const member = await build5Db().doc(`${COL.MEMBER}/${owner}`).get(); diff --git a/packages/functions/src/controls/collection/collection.approve.control.ts b/packages/functions/src/controls/collection/collection.approve.control.ts index 24f49232fc..415b0ecdfd 100644 --- a/packages/functions/src/controls/collection/collection.approve.control.ts +++ b/packages/functions/src/controls/collection/collection.approve.control.ts @@ -1,10 +1,11 @@ import { build5Db } from '@build-5/database'; import { ApproveCollectionRequest, COL, Collection, WenError } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian } from '../../utils/token.utils'; export const approveCollectionControl = async ( - owner: string, + { owner }: Context, params: ApproveCollectionRequest, ): Promise => { const collectionDocRef = build5Db().doc(`${COL.COLLECTION}/${params.uid}`); diff --git a/packages/functions/src/controls/collection/collection.create.control.ts b/packages/functions/src/controls/collection/collection.create.control.ts index 32e29fa0f0..a07c41bb2b 100644 --- a/packages/functions/src/controls/collection/collection.create.control.ts +++ b/packages/functions/src/controls/collection/collection.create.control.ts @@ -13,15 +13,19 @@ import { SUB_COL, WenError, } from '@build-5/interfaces'; -import { hasStakedSoonTokens } from '../../services/stake.service'; +import { Context } from '../../runtime/firebase/common'; +import { hasStakedTokens } from '../../services/stake.service'; import { assertSpaceHasValidAddress } from '../../utils/address.utils'; import { dateToTimestamp, serverTime } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; import { populateTokenUidOnDiscounts } from './common'; -export const createCollectionControl = async (owner: string, params: CreateCollectionRequest) => { - const hasStakedSoons = await hasStakedSoonTokens(owner); +export const createCollectionControl = async ( + { project, owner }: Context, + params: CreateCollectionRequest, +) => { + const hasStakedSoons = await hasStakedTokens(project, owner); if (!hasStakedSoons) { throw invalidArgument(WenError.no_staked_soon); } diff --git a/packages/functions/src/controls/collection/collection.reject.control.ts b/packages/functions/src/controls/collection/collection.reject.control.ts index 0349265e6c..9b004b8372 100644 --- a/packages/functions/src/controls/collection/collection.reject.control.ts +++ b/packages/functions/src/controls/collection/collection.reject.control.ts @@ -1,11 +1,12 @@ import { build5Db } from '@build-5/database'; import { COL, Collection, RejectCollectionRequest, WenError } from '@build-5/interfaces'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian } from '../../utils/token.utils'; export const rejectCollectionControl = async ( - owner: string, + { owner }: Context, params: RejectCollectionRequest, ): Promise => { const collectionDocRef = build5Db().doc(`${COL.COLLECTION}/${params.uid}`); diff --git a/packages/functions/src/controls/collection/collection.update.control.ts b/packages/functions/src/controls/collection/collection.update.control.ts index e119e60505..27c61b91dc 100644 --- a/packages/functions/src/controls/collection/collection.update.control.ts +++ b/packages/functions/src/controls/collection/collection.update.control.ts @@ -14,14 +14,14 @@ import dayjs from 'dayjs'; import { isEmpty, last, set } from 'lodash'; import { updateMintedCollectionSchemaObject } from '../../runtime/firebase/collection/CollectionUpdateMintedRequestSchema'; import { updateCollectionSchemaObject } from '../../runtime/firebase/collection/CollectionUpdateRequestSchema'; -import { UidSchemaObject } from '../../runtime/firebase/common'; +import { Context, UidSchemaObject } from '../../runtime/firebase/common'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { assertValidationAsync } from '../../utils/schema.utils'; import { assertIsGuardian } from '../../utils/token.utils'; import { populateTokenUidOnDiscounts } from './common'; -export const updateCollectionControl = async (owner: string, rawParams: UidSchemaObject) => { +export const updateCollectionControl = async ({ owner }: Context, rawParams: UidSchemaObject) => { const collectionDocRef = build5Db().doc(`${COL.COLLECTION}/${rawParams.uid}`); const collection = await collectionDocRef.get(); if (!collection) { diff --git a/packages/functions/src/controls/credit/credit.controller.ts b/packages/functions/src/controls/credit/credit.controller.ts index f3a157a677..8e3fb23937 100644 --- a/packages/functions/src/controls/credit/credit.controller.ts +++ b/packages/functions/src/controls/credit/credit.controller.ts @@ -13,13 +13,14 @@ import { WenError, } from '@build-5/interfaces'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { WalletService } from '../../services/wallet/wallet'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; export const creditUnrefundableControl = ( - owner: string, + { owner }: Context, params: CreditUnrefundableRequest, ): Promise => build5Db().runTransaction(async (transaction) => { diff --git a/packages/functions/src/controls/member/member.create.ts b/packages/functions/src/controls/member/member.create.ts index ec154bb88a..9be3a9be6e 100644 --- a/packages/functions/src/controls/member/member.create.ts +++ b/packages/functions/src/controls/member/member.create.ts @@ -1,8 +1,9 @@ -import { build5Db } from '@build-5/database'; import { COL, Member } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { getRandomNonce } from '../../utils/wallet.utils'; +import { build5Db } from '@build-5/database'; -export const createMemberControl = async (owner: string) => { +export const createMemberControl = async ({ owner }: Context) => { const memberDocRef = build5Db().collection(COL.MEMBER).doc(owner); const member = await memberDocRef.get(); diff --git a/packages/functions/src/controls/member/member.update.ts b/packages/functions/src/controls/member/member.update.ts index 3d512458cd..0dbc07f1ad 100644 --- a/packages/functions/src/controls/member/member.update.ts +++ b/packages/functions/src/controls/member/member.update.ts @@ -8,11 +8,12 @@ import { NftStatus, WenError, } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { invalidArgument } from '../../utils/error.utils'; import { cleanupParams } from '../../utils/schema.utils'; export const updateMemberControl = async ( - owner: string, + { owner }: Context, params: MemberUpdateRequest, ): Promise => { const memberDocRef = build5Db().doc(`${COL.MEMBER}/${owner}`); diff --git a/packages/functions/src/controls/nft/nft.bid.control.ts b/packages/functions/src/controls/nft/nft.bid.control.ts index 9c8b8df9d1..570c1d719f 100644 --- a/packages/functions/src/controls/nft/nft.bid.control.ts +++ b/packages/functions/src/controls/nft/nft.bid.control.ts @@ -1,12 +1,12 @@ import { build5Db } from '@build-5/database'; import { COL, NftBidRequest, Transaction, WenError } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { createNftBidOrder } from '../../services/payment/tangle-service/nft/nft-bid.service'; import { invalidArgument } from '../../utils/error.utils'; export const nftBidControl = async ( - owner: string, + { owner, ip }: Context, params: NftBidRequest, - customParams?: Record, ): Promise => { const memberDocRef = build5Db().doc(`${COL.MEMBER}/${owner}`); const member = await memberDocRef.get(); @@ -14,11 +14,7 @@ export const nftBidControl = async ( throw invalidArgument(WenError.member_does_not_exists); } - const bidTransaction = await createNftBidOrder( - params.nft, - owner, - (customParams?.ip as string) || '', - ); + const bidTransaction = await createNftBidOrder(params.nft, owner, ip || ''); const transactionDocRef = build5Db().doc(`${COL.TRANSACTION}/${bidTransaction.uid}`); await transactionDocRef.create(bidTransaction); diff --git a/packages/functions/src/controls/nft/nft.create.ts b/packages/functions/src/controls/nft/nft.create.ts index a2955ac250..1fcd63555b 100644 --- a/packages/functions/src/controls/nft/nft.create.ts +++ b/packages/functions/src/controls/nft/nft.create.ts @@ -12,17 +12,21 @@ import { } from '@build-5/interfaces'; import dayjs from 'dayjs'; import { isEmpty } from 'lodash'; +import { Context } from '../../runtime/firebase/common'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; -export const createNftControl = async (owner: string, params: NftCreateRequest): Promise => { +export const createNftControl = async ( + { owner }: Context, + params: NftCreateRequest, +): Promise => { const collection = await getCollection(owner, params.collection as string); return await processOneCreateNft(params, collection, collection.total + 1); }; export const createBatchNftControl = async ( - owner: string, + { owner }: Context, params: NftCreateRequest[], ): Promise => { const collection = await getCollection(owner, params[0].collection); diff --git a/packages/functions/src/controls/nft/nft.deposit.ts b/packages/functions/src/controls/nft/nft.deposit.ts index fbb39f5174..4bcea0e4c9 100644 --- a/packages/functions/src/controls/nft/nft.deposit.ts +++ b/packages/functions/src/controls/nft/nft.deposit.ts @@ -10,12 +10,13 @@ import { TransactionValidationType, } from '@build-5/interfaces'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { WalletService } from '../../services/wallet/wallet'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; export const depositNftControl = async ( - owner: string, + { owner }: Context, params: NftDepositRequest, ): Promise => { const network = params.network as Network; diff --git a/packages/functions/src/controls/nft/nft.puchase.control.ts b/packages/functions/src/controls/nft/nft.puchase.control.ts index 01048693ef..a41ea71eef 100644 --- a/packages/functions/src/controls/nft/nft.puchase.control.ts +++ b/packages/functions/src/controls/nft/nft.puchase.control.ts @@ -1,18 +1,13 @@ import { build5Db } from '@build-5/database'; import { COL, NftPurchaseRequest, Transaction } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { createNftPuchaseOrder } from '../../services/payment/tangle-service/nft/nft-purchase.service'; export const orderNftControl = async ( - owner: string, + { owner, ip }: Context, params: NftPurchaseRequest, - customParams?: Record, ): Promise => { - const order = await createNftPuchaseOrder( - params.collection, - params.nft, - owner, - (customParams?.ip || '') as string, - ); + const order = await createNftPuchaseOrder(params.collection, params.nft, owner, ip || ''); const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${order.uid}`); await orderDocRef.create(order); diff --git a/packages/functions/src/controls/nft/nft.set.for.sale.ts b/packages/functions/src/controls/nft/nft.set.for.sale.ts index 8d58cf047d..27c264162e 100644 --- a/packages/functions/src/controls/nft/nft.set.for.sale.ts +++ b/packages/functions/src/controls/nft/nft.set.for.sale.ts @@ -1,10 +1,11 @@ import { build5Db } from '@build-5/database'; import { COL, Member, Nft, NftSetForSaleRequest, WenError } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { getNftSetForSaleParams } from '../../services/payment/tangle-service/nft/nft-set-for-sale.service'; import { invalidArgument } from '../../utils/error.utils'; export const setForSaleNftControl = async ( - owner: string, + { owner }: Context, params: NftSetForSaleRequest, ): Promise => { const memberDocRef = build5Db().doc(`${COL.MEMBER}/${owner}`); diff --git a/packages/functions/src/controls/nft/nft.stake.ts b/packages/functions/src/controls/nft/nft.stake.ts index 75a3821f7c..13c9c11a60 100644 --- a/packages/functions/src/controls/nft/nft.stake.ts +++ b/packages/functions/src/controls/nft/nft.stake.ts @@ -1,9 +1,10 @@ import { build5Db } from '@build-5/database'; import { COL, Network, NftStakeRequest, StakeType, Transaction } from '@build-5/interfaces'; -import { createNftStakeOrder } from '../../services/payment/nft/nft-stake-service'; +import { Context } from '../../runtime/firebase/common'; +import { createNftStakeOrder } from '../../services/payment/nft/nft-stake.service'; export const nftStakeControl = async ( - owner: string, + { owner }: Context, params: NftStakeRequest, ): Promise => { const order = await createNftStakeOrder( diff --git a/packages/functions/src/controls/nft/nft.update.unsold.ts b/packages/functions/src/controls/nft/nft.update.unsold.ts index 3e1c0ee142..5377ed41be 100644 --- a/packages/functions/src/controls/nft/nft.update.unsold.ts +++ b/packages/functions/src/controls/nft/nft.update.unsold.ts @@ -1,10 +1,11 @@ import { build5Db } from '@build-5/database'; import { COL, Nft, NftUpdateUnsoldRequest, WenError } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian } from '../../utils/token.utils'; export const updateUnsoldNftControl = async ( - owner: string, + { owner }: Context, params: NftUpdateUnsoldRequest, ): Promise => build5Db().runTransaction(async (transaction) => { diff --git a/packages/functions/src/controls/nft/nft.withdraw.ts b/packages/functions/src/controls/nft/nft.withdraw.ts index 7043cc8921..3f45922e04 100644 --- a/packages/functions/src/controls/nft/nft.withdraw.ts +++ b/packages/functions/src/controls/nft/nft.withdraw.ts @@ -9,11 +9,12 @@ import { NftWithdrawRequest, WenError, } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { createNftWithdrawOrder } from '../../services/payment/tangle-service/nft/nft-purchase.service'; import { assertMemberHasValidAddress, getAddress } from '../../utils/address.utils'; import { invalidArgument } from '../../utils/error.utils'; -export const withdrawNftControl = async (owner: string, params: NftWithdrawRequest) => +export const withdrawNftControl = async ({ owner }: Context, params: NftWithdrawRequest) => build5Db().runTransaction(async (transaction) => { const nftDocRef = build5Db().doc(`${COL.NFT}/${params.nft}`); const nft = await transaction.get(nftDocRef); diff --git a/packages/functions/src/controls/project/init.soon.project.ts b/packages/functions/src/controls/project/init.soon.project.ts new file mode 100644 index 0000000000..009cafeebe --- /dev/null +++ b/packages/functions/src/controls/project/init.soon.project.ts @@ -0,0 +1,81 @@ +import { build5Db } from '@build-5/database'; +import { + COL, + MIN_IOTA_AMOUNT, + ProjectBilling, + ProjectGuardian, + SOON_PROJECT_ID, + SUB_COL, +} from '@build-5/interfaces'; +import dayjs from 'dayjs'; +import * as functions from 'firebase-functions/v2'; +import jwt from 'jsonwebtoken'; +import { getJwtSecretKey, isProdEnv } from '../../utils/config.utils'; +import { dateToTimestamp } from '../../utils/dateTime.utils'; +import { getRandomEthAddress } from '../../utils/wallet.utils'; + +const GUARDIAN_ID = '0x551fd2c7c7bf356bac194587dab2fcd46420054b'; + +export const initSoonProject = functions.https.onRequest(async (req, res) => { + const project = isProdEnv() ? prodProject : testProject; + const projectDocRef = build5Db().doc(`${COL.PROJECT}/${project.uid}`); + + if ((await projectDocRef.get()) !== undefined) { + return; + } + + const batch = build5Db().batch(); + + batch.create(projectDocRef, project); + + const guardianDocRef = projectDocRef.collection(SUB_COL.GUARDIANS).doc(GUARDIAN_ID); + const guardian: ProjectGuardian = { + uid: GUARDIAN_ID, + createdOn: dateToTimestamp(dayjs()), + parentCol: COL.PROJECT, + parentId: project.uid, + }; + batch.create(guardianDocRef, guardian); + + const rawJwt = { uid: GUARDIAN_ID, project: project.uid, iat: dayjs().unix() }; + const signed = jwt.sign(rawJwt, getJwtSecretKey()); + const apiKey = { + uid: getRandomEthAddress(), + token: signed, + parentCol: COL.PROJECT, + parentId: project.uid, + createdOn: dateToTimestamp(dayjs()), + }; + const apiKeyDocRef = projectDocRef.collection(SUB_COL._API_KEY).doc(); + batch.create(apiKeyDocRef, apiKey); + + await batch.commit(); +}); + +const testProject = { + uid: SOON_PROJECT_ID, + name: 'Soonaverse', + createdBy: GUARDIAN_ID, + deactivated: false, + config: { + billing: ProjectBilling.TOKEN_BASE, + tiers: [0, 0, 0, 0, 0].map((v) => v * MIN_IOTA_AMOUNT), + tokenTradingFeeDiscountPercentage: [0, 0, 0, 0, 0], + baseTokenSymbol: 'SOON', + baseTokenUid: '0x36ebf336f63f64718d088ec31c1db3151e0d8317', + }, +}; + +const prodProject = { + uid: SOON_PROJECT_ID, + name: 'Soonaverse', + createdBy: GUARDIAN_ID, + deactivated: false, + config: { + billing: ProjectBilling.TOKEN_BASE, + tiers: [0, 10, 4000, 6000, 15000].map((v) => v * MIN_IOTA_AMOUNT), + tokenTradingFeeDiscountPercentage: [0, 25, 50, 75, 100], + baseTokenSymbol: 'SOON', + baseTokenUid: '0x9600b5afbb84f15e0d4c0f90ea60b2b8d7bd0f1e', + }, +}; diff --git a/packages/functions/src/controls/project/project.create.control.ts b/packages/functions/src/controls/project/project.create.control.ts new file mode 100644 index 0000000000..39a9473e75 --- /dev/null +++ b/packages/functions/src/controls/project/project.create.control.ts @@ -0,0 +1,69 @@ +import { build5Db } from '@build-5/database'; +import { + COL, + Project, + ProjectBilling, + ProjectCreateRequest, + ProjectGuardian, + SUB_COL, + WenError, +} from '@build-5/interfaces'; +import dayjs from 'dayjs'; +import jwt from 'jsonwebtoken'; +import { Context } from '../../runtime/firebase/common'; +import { getJwtSecretKey } from '../../utils/config.utils'; +import { dateToTimestamp } from '../../utils/dateTime.utils'; +import { invalidArgument } from '../../utils/error.utils'; +import { getTokenBySymbol } from '../../utils/token.utils'; +import { getRandomEthAddress } from '../../utils/wallet.utils'; + +export const createProjectControl = async ({ owner }: Context, params: ProjectCreateRequest) => { + const memberDocRef = build5Db().doc(`${COL.MEMBER}/${owner}`); + const member = await memberDocRef.get(); + if (!member) { + throw invalidArgument(WenError.member_does_not_exists); + } + + const batch = build5Db().batch(); + + const projectData = { + ...params, + uid: getRandomEthAddress(), + createdBy: owner, + deactivated: false, + } as Project; + + if (projectData.config.billing === ProjectBilling.TOKEN_BASE) { + const token = await getTokenBySymbol(projectData.config.baseTokenSymbol!); + + if (token?.uid !== projectData.config.baseTokenUid) { + throw invalidArgument(WenError.token_does_not_exist); + } + } + const projectDocRef = build5Db().doc(`${COL.PROJECT}/${projectData.uid}`); + batch.create(projectDocRef, projectData); + + const guardianDocRef = projectDocRef.collection(SUB_COL.GUARDIANS).doc(owner); + const guardian: ProjectGuardian = { + uid: owner, + createdOn: dateToTimestamp(dayjs()), + parentCol: COL.PROJECT, + parentId: projectData.uid, + }; + batch.create(guardianDocRef, guardian); + + const rawJwt = { uid: owner, project: projectData.uid, iat: dayjs().unix() }; + const token = jwt.sign(rawJwt, getJwtSecretKey()); + const apiKey = { + uid: getRandomEthAddress(), + parentCol: COL.PROJECT, + parentId: projectData.uid, + token, + }; + const apiKeyDocRef = projectDocRef.collection(SUB_COL._API_KEY).doc(); + batch.create(apiKeyDocRef, apiKey); + + await batch.commit(); + + return { project: await projectDocRef.get(), token }; +}; diff --git a/packages/functions/src/controls/project/project.deactivate.control.ts b/packages/functions/src/controls/project/project.deactivate.control.ts new file mode 100644 index 0000000000..e181ef42b6 --- /dev/null +++ b/packages/functions/src/controls/project/project.deactivate.control.ts @@ -0,0 +1,15 @@ +import { build5Db } from '@build-5/database'; +import { COL, Project, ProjectDeactivateRequest } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; +import { assertIsProjectGuardian } from '../../utils/common.utils'; + +export const deactivateProjectControl = async ( + { project, owner }: Context, + params: ProjectDeactivateRequest, +): Promise => { + await assertIsProjectGuardian(project, owner); + + const projectDocRef = build5Db().doc(`${COL.PROJECT}/${params.project}`); + await projectDocRef.update({ deactivated: true }); + return (await projectDocRef.get())!; +}; diff --git a/packages/functions/src/controls/proposal/approve.reject.proposal.ts b/packages/functions/src/controls/proposal/approve.reject.proposal.ts index e8c1d6a68c..dac7e495dc 100644 --- a/packages/functions/src/controls/proposal/approve.reject.proposal.ts +++ b/packages/functions/src/controls/proposal/approve.reject.proposal.ts @@ -1,10 +1,11 @@ import { build5Db } from '@build-5/database'; import { ApproveProposalRequest, COL, Proposal, RejectProposalRequest } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { getProposalApprovalData } from '../../services/payment/tangle-service/proposal/ProposalApporvalService'; export const proposalApprovalControl = (approve: boolean) => - async (owner: string, params: ApproveProposalRequest | RejectProposalRequest) => { + async ({ owner }: Context, params: ApproveProposalRequest | RejectProposalRequest) => { const data = await getProposalApprovalData(owner, params.uid, approve); const proposalDocRef = build5Db().doc(`${COL.PROPOSAL}/${params.uid}`); await proposalDocRef.update(data); diff --git a/packages/functions/src/controls/proposal/create.proposal.ts b/packages/functions/src/controls/proposal/create.proposal.ts index 601c840f65..519e77cff7 100644 --- a/packages/functions/src/controls/proposal/create.proposal.ts +++ b/packages/functions/src/controls/proposal/create.proposal.ts @@ -1,8 +1,9 @@ import { build5Db } from '@build-5/database'; import { COL, Proposal, ProposalCreateRequest, SUB_COL } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { createProposal } from '../../services/payment/tangle-service/proposal/ProposalCreateService'; -export const createProposalControl = async (owner: string, params: ProposalCreateRequest) => { +export const createProposalControl = async ({ owner }: Context, params: ProposalCreateRequest) => { const { proposal, proposalOwner } = await createProposal(owner, { ...params }); const proposalDocRef = build5Db().doc(`${COL.PROPOSAL}/${proposal.uid}`); diff --git a/packages/functions/src/controls/proposal/vote.on.proposal.ts b/packages/functions/src/controls/proposal/vote.on.proposal.ts index 0ee4759105..b58f082148 100644 --- a/packages/functions/src/controls/proposal/vote.on.proposal.ts +++ b/packages/functions/src/controls/proposal/vote.on.proposal.ts @@ -8,6 +8,7 @@ import { Transaction, WenError, } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { getProposal, getProposalMember, @@ -19,7 +20,7 @@ import { invalidArgument } from '../../utils/error.utils'; import { getTokenForSpace } from '../../utils/token.utils'; export const voteOnProposalControl = async ( - owner: string, + { owner }: Context, params: ProposalVoteRequest, ): Promise => { const proposal = await getProposal(params.uid); diff --git a/packages/functions/src/controls/rank.control.ts b/packages/functions/src/controls/rank.control.ts index 79b9b15eb2..b767a82ec7 100644 --- a/packages/functions/src/controls/rank.control.ts +++ b/packages/functions/src/controls/rank.control.ts @@ -1,13 +1,14 @@ import { build5Db } from '@build-5/database'; import { COL, Collection, Rank, RankRequest, SUB_COL, Token, WenError } from '@build-5/interfaces'; import { set } from 'lodash'; -import { hasStakedSoonTokens } from '../services/stake.service'; +import { Context } from '../runtime/firebase/common'; +import { hasStakedTokens } from '../services/stake.service'; import { getRankingSpace } from '../utils/config.utils'; import { invalidArgument } from '../utils/error.utils'; import { assertIsGuardian } from '../utils/token.utils'; -export const rankControl = async (owner: string, params: RankRequest) => { - const hasStakedSoons = await hasStakedSoonTokens(owner); +export const rankControl = async ({ project, owner }: Context, params: RankRequest) => { + const hasStakedSoons = await hasStakedTokens(project, owner); if (!hasStakedSoons) { throw invalidArgument(WenError.no_staked_soon); } diff --git a/packages/functions/src/controls/space/member.accept.control.ts b/packages/functions/src/controls/space/member.accept.control.ts index df8f4c72e5..72cd5eaa2e 100644 --- a/packages/functions/src/controls/space/member.accept.control.ts +++ b/packages/functions/src/controls/space/member.accept.control.ts @@ -1,8 +1,12 @@ import { build5Db } from '@build-5/database'; import { COL, SpaceMember, SpaceMemberUpsertRequest, SUB_COL } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { acceptSpaceMember } from '../../services/payment/tangle-service/space/SpaceAcceptMemberService'; -export const acceptSpaceMemberControl = async (owner: string, params: SpaceMemberUpsertRequest) => { +export const acceptSpaceMemberControl = async ( + { owner }: Context, + params: SpaceMemberUpsertRequest, +) => { const { spaceMember, space } = await acceptSpaceMember(owner, params.uid, params.member); const spaceDocRef = build5Db().doc(`${COL.SPACE}/${params.uid}`); diff --git a/packages/functions/src/controls/space/member.block.control.ts b/packages/functions/src/controls/space/member.block.control.ts index 4eeb55f056..3dce564113 100644 --- a/packages/functions/src/controls/space/member.block.control.ts +++ b/packages/functions/src/controls/space/member.block.control.ts @@ -1,8 +1,9 @@ import { build5Db } from '@build-5/database'; import { COL, SpaceMember, SpaceMemberUpsertRequest, SUB_COL } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { getBlockMemberUpdateData } from '../../services/payment/tangle-service/space/SpaceBlockMemberService'; -export const blockMemberControl = async (owner: string, params: SpaceMemberUpsertRequest) => { +export const blockMemberControl = async ({ owner }: Context, params: SpaceMemberUpsertRequest) => { const member = params.member; const { space, blockedMember } = await getBlockMemberUpdateData(owner, params.uid, member); diff --git a/packages/functions/src/controls/space/member.decline.control.ts b/packages/functions/src/controls/space/member.decline.control.ts index 9e09abcff2..d04716b357 100644 --- a/packages/functions/src/controls/space/member.decline.control.ts +++ b/packages/functions/src/controls/space/member.decline.control.ts @@ -1,8 +1,12 @@ -import { build5Db } from '@build-5/database'; import { COL, SUB_COL, SpaceMemberUpsertRequest } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { assertIsGuardian } from '../../utils/token.utils'; +import { build5Db } from '@build-5/database'; -export const declineMemberControl = async (owner: string, params: SpaceMemberUpsertRequest) => { +export const declineMemberControl = async ( + { owner }: Context, + params: SpaceMemberUpsertRequest, +) => { await assertIsGuardian(params.uid, owner); const spaceDocRef = build5Db().doc(`${COL.SPACE}/${params.uid}`); diff --git a/packages/functions/src/controls/space/member.leave.control.ts b/packages/functions/src/controls/space/member.leave.control.ts index 5d5bdf95de..72434f2ecb 100644 --- a/packages/functions/src/controls/space/member.leave.control.ts +++ b/packages/functions/src/controls/space/member.leave.control.ts @@ -1,8 +1,9 @@ import { build5Db } from '@build-5/database'; import { COL, SUB_COL, SpaceLeaveRequest } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { getLeaveSpaceData } from '../../services/payment/tangle-service/space/SpaceLeaveService'; -export const leaveSpaceControl = async (owner: string, params: SpaceLeaveRequest) => { +export const leaveSpaceControl = async ({ owner }: Context, params: SpaceLeaveRequest) => { const { space, member } = await getLeaveSpaceData(owner, params.uid); const spaceDocRef = build5Db().doc(`${COL.SPACE}/${params.uid}`); diff --git a/packages/functions/src/controls/space/member.unblock.control.ts b/packages/functions/src/controls/space/member.unblock.control.ts index 37d2dd4a8c..9e4de96769 100644 --- a/packages/functions/src/controls/space/member.unblock.control.ts +++ b/packages/functions/src/controls/space/member.unblock.control.ts @@ -1,8 +1,12 @@ -import { build5Db } from '@build-5/database'; import { COL, SUB_COL, SpaceMemberUpsertRequest } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { assertIsGuardian } from '../../utils/token.utils'; +import { build5Db } from '@build-5/database'; -export const unblockMemberControl = async (owner: string, params: SpaceMemberUpsertRequest) => { +export const unblockMemberControl = async ( + { owner }: Context, + params: SpaceMemberUpsertRequest, +) => { await assertIsGuardian(params.uid, owner); const spaceDocRef = build5Db().doc(`${COL.SPACE}/${params.uid}`); diff --git a/packages/functions/src/controls/space/space.claim.control.ts b/packages/functions/src/controls/space/space.claim.control.ts index 40d56a73cf..2b458d39ad 100644 --- a/packages/functions/src/controls/space/space.claim.control.ts +++ b/packages/functions/src/controls/space/space.claim.control.ts @@ -1,4 +1,3 @@ -import { build5Db } from '@build-5/database'; import { COL, Collection, @@ -12,13 +11,15 @@ import { WenError, } from '@build-5/interfaces'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { WalletService } from '../../services/wallet/wallet'; import { generateRandomAmount } from '../../utils/common.utils'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; +import { build5Db } from '@build-5/database'; -export const claimSpaceControl = async (owner: string, params: SpaceClaimRequest) => { +export const claimSpaceControl = async ({ owner }: Context, params: SpaceClaimRequest) => { const spaceDocRef = build5Db().doc(`${COL.SPACE}/${params.uid}`); const space = await spaceDocRef.get(); if (!space) { diff --git a/packages/functions/src/controls/space/space.create.control.ts b/packages/functions/src/controls/space/space.create.control.ts index e923654510..49f3d8c4cc 100644 --- a/packages/functions/src/controls/space/space.create.control.ts +++ b/packages/functions/src/controls/space/space.create.control.ts @@ -1,9 +1,10 @@ -import { build5Db } from '@build-5/database'; import { COL, SUB_COL, Space, SpaceCreateRequest } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { getCreateSpaceData } from '../../services/payment/tangle-service/space/SpaceCreateService'; +import { build5Db } from '@build-5/database'; export const createSpaceControl = async ( - owner: string, + { owner }: Context, params: SpaceCreateRequest, ): Promise => { const { space, guardian, member } = await getCreateSpaceData(owner, { ...params }); diff --git a/packages/functions/src/controls/space/space.guardian.edit.control.ts b/packages/functions/src/controls/space/space.guardian.edit.control.ts index bc7c6347a9..5c2a3634a6 100644 --- a/packages/functions/src/controls/space/space.guardian.edit.control.ts +++ b/packages/functions/src/controls/space/space.guardian.edit.control.ts @@ -6,10 +6,12 @@ import { SUB_COL, SpaceMemberUpsertRequest, } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { addRemoveGuardian } from '../../services/payment/tangle-service/space/SpaceGuardianService'; export const editGuardianControl = - (type: ProposalType) => async (owner: string, params: SpaceMemberUpsertRequest) => { + (type: ProposalType) => + async ({ owner }: Context, params: SpaceMemberUpsertRequest) => { const { proposal, voteTransaction, members } = await addRemoveGuardian( owner, { ...params }, diff --git a/packages/functions/src/controls/space/space.join.control.ts b/packages/functions/src/controls/space/space.join.control.ts index b5650f633e..e1b85ac6d8 100644 --- a/packages/functions/src/controls/space/space.join.control.ts +++ b/packages/functions/src/controls/space/space.join.control.ts @@ -1,9 +1,10 @@ import { build5Db } from '@build-5/database'; import { COL, Space, SpaceJoinRequest, SUB_COL, WenError } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { getJoinSpaceData } from '../../services/payment/tangle-service/space/SpaceJoinService'; import { invalidArgument } from '../../utils/error.utils'; -export const joinSpaceControl = async (owner: string, params: SpaceJoinRequest) => { +export const joinSpaceControl = async ({ owner }: Context, params: SpaceJoinRequest) => { const spaceDocRef = build5Db().doc(`${COL.SPACE}/${params.uid}`); const space = await spaceDocRef.get(); if (!space) { diff --git a/packages/functions/src/controls/space/space.update.control.ts b/packages/functions/src/controls/space/space.update.control.ts index a117b76419..53a3c16fbf 100644 --- a/packages/functions/src/controls/space/space.update.control.ts +++ b/packages/functions/src/controls/space/space.update.control.ts @@ -1,4 +1,3 @@ -import { build5Db } from '@build-5/database'; import { BaseProposalAnswerValue, COL, @@ -18,14 +17,16 @@ import { } from '@build-5/interfaces'; import dayjs from 'dayjs'; import { get, startCase } from 'lodash'; +import { Context } from '../../runtime/firebase/common'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { cleanupParams } from '../../utils/schema.utils'; import { hasActiveEditProposal } from '../../utils/space.utils'; import { assertIsGuardian, getTokenForSpace } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; +import { build5Db } from '@build-5/database'; -export const updateSpaceControl = async (owner: string, params: SpaceUpdateRequest) => { +export const updateSpaceControl = async ({ owner }: Context, params: SpaceUpdateRequest) => { const spaceDocRef = build5Db().doc(`${COL.SPACE}/${params.uid}`); const space = await spaceDocRef.get(); diff --git a/packages/functions/src/controls/stake/stake.deposit.ts b/packages/functions/src/controls/stake/stake.deposit.ts index 88e76df46b..2aa7367a7a 100644 --- a/packages/functions/src/controls/stake/stake.deposit.ts +++ b/packages/functions/src/controls/stake/stake.deposit.ts @@ -1,8 +1,9 @@ -import { build5Db } from '@build-5/database'; import { COL, StakeType, TokenStakeRequest } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { createStakeOrder } from '../../services/payment/tangle-service/token/stake.service'; +import { build5Db } from '@build-5/database'; -export const depositStakeControl = async (owner: string, params: TokenStakeRequest) => { +export const depositStakeControl = async ({ owner }: Context, params: TokenStakeRequest) => { const order = await createStakeOrder( owner, params.symbol, diff --git a/packages/functions/src/controls/stake/stake.reward.revoke.ts b/packages/functions/src/controls/stake/stake.reward.revoke.ts index 01f56bb16c..6bb058d545 100644 --- a/packages/functions/src/controls/stake/stake.reward.revoke.ts +++ b/packages/functions/src/controls/stake/stake.reward.revoke.ts @@ -19,13 +19,14 @@ import { } from '@build-5/interfaces'; import dayjs from 'dayjs'; import { uniq } from 'lodash'; +import { Context } from '../../runtime/firebase/common'; import { dateToTimestamp, serverTime } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; export const removeStakeRewardControl = async ( - owner: string, + { owner }: Context, params: TokenStakeRewardsRemoveRequest, ) => { const stakeRewardIds = params.stakeRewardIds as string[]; diff --git a/packages/functions/src/controls/stake/stake.reward.ts b/packages/functions/src/controls/stake/stake.reward.ts index 99da2ad00e..d311ba0b5d 100644 --- a/packages/functions/src/controls/stake/stake.reward.ts +++ b/packages/functions/src/controls/stake/stake.reward.ts @@ -8,12 +8,13 @@ import { WenError, } from '@build-5/interfaces'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; -export const stakeRewardControl = async (owner: string, params: TokenStakeRewardsRequest) => { +export const stakeRewardControl = async ({ owner }: Context, params: TokenStakeRewardsRequest) => { const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${params.token}`); const token = await tokenDocRef.get(); if (!token) { diff --git a/packages/functions/src/controls/token-minting/airdrop-minted-token.ts b/packages/functions/src/controls/token-minting/airdrop-minted-token.ts index 0853b9e38b..4718a67d5d 100644 --- a/packages/functions/src/controls/token-minting/airdrop-minted-token.ts +++ b/packages/functions/src/controls/token-minting/airdrop-minted-token.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { build5Db } from '@build-5/database'; import { COL, StakeType, @@ -18,6 +17,7 @@ import { HexHelper } from '@iota/util.js-next'; import bigInt from 'big-integer'; import dayjs from 'dayjs'; import { chunk } from 'lodash'; +import { Context } from '../../runtime/firebase/common'; import { CreateAirdropsRequest } from '../../runtime/firebase/token/base/TokenAirdropRequestSchema'; import { SmrWallet } from '../../services/wallet/SmrWalletService'; import { WalletService } from '../../services/wallet/wallet'; @@ -26,8 +26,12 @@ import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian, assertTokenApproved, assertTokenStatus } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; +import { build5Db } from '@build-5/database'; -export const airdropMintedTokenControl = async (owner: string, params: CreateAirdropsRequest) => { +export const airdropMintedTokenControl = async ( + { owner }: Context, + params: CreateAirdropsRequest, +) => { const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${params.token}`); await build5Db().runTransaction(async (transaction) => { const token = await transaction.get(tokenDocRef); diff --git a/packages/functions/src/controls/token-minting/claim-minted-token.control.ts b/packages/functions/src/controls/token-minting/claim-minted-token.control.ts index e507657146..87635d2972 100644 --- a/packages/functions/src/controls/token-minting/claim-minted-token.control.ts +++ b/packages/functions/src/controls/token-minting/claim-minted-token.control.ts @@ -1,9 +1,10 @@ import { build5Db } from '@build-5/database'; import { COL, ClaimAirdroppedTokensRequest } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { createMintedTokenAirdropCalimOrder } from '../../services/payment/tangle-service/token/token-claim.service'; export const claimMintedTokenControl = async ( - owner: string, + { owner }: Context, params: ClaimAirdroppedTokensRequest, ) => { const order = await createMintedTokenAirdropCalimOrder(owner, params.symbol); diff --git a/packages/functions/src/controls/token-minting/import-minted-token.ts b/packages/functions/src/controls/token-minting/import-minted-token.ts index c996859662..500dd12500 100644 --- a/packages/functions/src/controls/token-minting/import-minted-token.ts +++ b/packages/functions/src/controls/token-minting/import-minted-token.ts @@ -13,6 +13,7 @@ import { import { IndexerPluginClient } from '@iota/iota.js-next'; import dayjs from 'dayjs'; import { isEmpty } from 'lodash'; +import { Context } from '../../runtime/firebase/common'; import { SmrWallet } from '../../services/wallet/SmrWalletService'; import { WalletService } from '../../services/wallet/wallet'; import { generateRandomAmount } from '../../utils/common.utils'; @@ -21,7 +22,10 @@ import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; -export const importMintedTokenControl = async (owner: string, params: ImportMintedTokenRequest) => +export const importMintedTokenControl = async ( + { owner }: Context, + params: ImportMintedTokenRequest, +) => build5Db().runTransaction(async (transaction) => { await assertIsGuardian(params.space, owner); diff --git a/packages/functions/src/controls/token-minting/token-mint.control.ts b/packages/functions/src/controls/token-minting/token-mint.control.ts index 5c006b69bf..564329621f 100644 --- a/packages/functions/src/controls/token-minting/token-mint.control.ts +++ b/packages/functions/src/controls/token-minting/token-mint.control.ts @@ -1,4 +1,3 @@ -import { build5Db } from '@build-5/database'; import { COL, Member, @@ -15,6 +14,7 @@ import { } from '@build-5/interfaces'; import { TransactionHelper } from '@iota/iota.js-next'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { SmrWallet } from '../../services/wallet/SmrWalletService'; import { AddressDetails, WalletService } from '../../services/wallet/wallet'; import { assertMemberHasValidAddress } from '../../utils/address.utils'; @@ -33,8 +33,9 @@ import { getUnclaimedAirdropTotalValue, } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; +import { build5Db } from '@build-5/database'; -export const mintTokenControl = (owner: string, params: TokenMintRequest) => +export const mintTokenControl = ({ owner }: Context, params: TokenMintRequest) => build5Db().runTransaction(async (transaction) => { const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${params.token}`); const token = await transaction.get(tokenDocRef); @@ -83,7 +84,8 @@ export const mintTokenControl = (owner: string, params: TokenMintRequest) => tokensInVault: totalDistributed, }, }; - transaction.create(build5Db().doc(`${COL.TRANSACTION}/${order.uid}`), order); + const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${order.uid}`); + transaction.create(orderDocRef, order); return order; }); diff --git a/packages/functions/src/controls/token-trading/token-trade-cancel.controller.ts b/packages/functions/src/controls/token-trading/token-trade-cancel.controller.ts index b28216d938..5c1069708e 100644 --- a/packages/functions/src/controls/token-trading/token-trade-cancel.controller.ts +++ b/packages/functions/src/controls/token-trading/token-trade-cancel.controller.ts @@ -6,18 +6,15 @@ import { TokenTradeOrderStatus, WenError, } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { invalidArgument } from '../../utils/error.utils'; import { cancelTradeOrderUtil } from '../../utils/token-trade.utils'; -export const cancelTradeOrderControl = (owner: string, params: CancelTokenTradeOrderRequest) => +export const cancelTradeOrderControl = ({ owner }: Context, params: CancelTokenTradeOrderRequest) => build5Db().runTransaction(async (transaction) => { const tradeOrderDocRef = build5Db().doc(`${COL.TOKEN_MARKET}/${params.uid}`); const tradeOrder = await transaction.get(tradeOrderDocRef); - if ( - !tradeOrder || - tradeOrder.owner !== owner || - tradeOrder.status !== TokenTradeOrderStatus.ACTIVE - ) { + if (tradeOrder?.owner !== owner || tradeOrder.status !== TokenTradeOrderStatus.ACTIVE) { throw invalidArgument(WenError.invalid_params); } return await cancelTradeOrderUtil(transaction, tradeOrder); diff --git a/packages/functions/src/controls/token-trading/token-trade.controller.ts b/packages/functions/src/controls/token-trading/token-trade.controller.ts index 9f0660e73b..124581629c 100644 --- a/packages/functions/src/controls/token-trading/token-trade.controller.ts +++ b/packages/functions/src/controls/token-trading/token-trade.controller.ts @@ -1,4 +1,3 @@ -import { build5Db } from '@build-5/database'; import { COL, SUB_COL, @@ -7,15 +6,13 @@ import { TradeTokenRequest, WenError, } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { createTokenTradeOrder } from '../../services/payment/tangle-service/token/token-trade.service'; import { invalidArgument } from '../../utils/error.utils'; import { getTokenBySymbol } from '../../utils/token.utils'; +import { build5Db } from '@build-5/database'; -export const tradeTokenControl = async ( - owner: string, - params: TradeTokenRequest, - customParams?: Record, -) => { +export const tradeTokenControl = async ({ owner, ip }: Context, params: TradeTokenRequest) => { let token = await getTokenBySymbol(params.symbol); return await build5Db().runTransaction(async (transaction) => { @@ -35,7 +32,7 @@ export const tradeTokenControl = async ( params.type as TokenTradeOrderType, params.count, params.price, - customParams?.ip as string | undefined, + ip, ); if (tradeOrder) { const orderDocRef = build5Db().doc(`${COL.TOKEN_MARKET}/${tradeOrder.uid}`); diff --git a/packages/functions/src/controls/token/token.airdrop.claim.ts b/packages/functions/src/controls/token/token.airdrop.claim.ts index 4d07640dcf..1313ca57d4 100644 --- a/packages/functions/src/controls/token/token.airdrop.claim.ts +++ b/packages/functions/src/controls/token/token.airdrop.claim.ts @@ -1,4 +1,3 @@ -import { build5Db } from '@build-5/database'; import { COL, ClaimPreMintedAirdroppedTokensRequest, @@ -14,15 +13,17 @@ import { } from '@build-5/interfaces'; import dayjs from 'dayjs'; import { isEmpty } from 'lodash'; +import { Context } from '../../runtime/firebase/common'; import { WalletService } from '../../services/wallet/wallet'; import { generateRandomAmount } from '../../utils/common.utils'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { assertTokenStatus, getUnclaimedDrops } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; +import { build5Db } from '@build-5/database'; export const claimAirdroppedTokenControl = async ( - owner: string, + { owner }: Context, params: ClaimPreMintedAirdroppedTokensRequest, ): Promise => { const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${params.token}`); diff --git a/packages/functions/src/controls/token/token.airdrop.ts b/packages/functions/src/controls/token/token.airdrop.ts index b81e8d4df3..ec0accf433 100644 --- a/packages/functions/src/controls/token/token.airdrop.ts +++ b/packages/functions/src/controls/token/token.airdrop.ts @@ -1,4 +1,3 @@ -import { build5Db } from '@build-5/database'; import { COL, StakeType, @@ -10,11 +9,13 @@ import { WenError, } from '@build-5/interfaces'; import { chunk } from 'lodash'; +import { Context } from '../../runtime/firebase/common'; import { CreateAirdropsRequest } from '../../runtime/firebase/token/base/TokenAirdropRequestSchema'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian, assertTokenApproved, assertTokenStatus } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; +import { build5Db } from '@build-5/database'; export interface TokenDropRequest { readonly vestingAt: Date; @@ -29,7 +30,7 @@ const hasAvailableTokenToAirdrop = (token: Token, count: number) => { return token.totalSupply - totalPublicSupply - token.totalAirdropped >= count; }; -export const airdropTokenControl = async (owner: string, params: CreateAirdropsRequest) => { +export const airdropTokenControl = async ({ owner }: Context, params: CreateAirdropsRequest) => { const chunks = chunk(params.drops, 200); for (const chunk of chunks) { await build5Db().runTransaction(async (transaction) => { diff --git a/packages/functions/src/controls/token/token.cancel.pub.sale.ts b/packages/functions/src/controls/token/token.cancel.pub.sale.ts index 5d8a33cc4c..8412c7224a 100644 --- a/packages/functions/src/controls/token/token.cancel.pub.sale.ts +++ b/packages/functions/src/controls/token/token.cancel.pub.sale.ts @@ -1,10 +1,14 @@ import { build5Db } from '@build-5/database'; import { COL, CanelPublicSaleRequest, Token, TokenStatus, WenError } from '@build-5/interfaces'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian } from '../../utils/token.utils'; -export const cancelPublicSaleControl = async (owner: string, params: CanelPublicSaleRequest) => { +export const cancelPublicSaleControl = async ( + { owner }: Context, + params: CanelPublicSaleRequest, +) => { const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${params.token}`); await build5Db().runTransaction(async (transaction) => { diff --git a/packages/functions/src/controls/token/token.create.ts b/packages/functions/src/controls/token/token.create.ts index cd97c2b89d..3481de67fc 100644 --- a/packages/functions/src/controls/token/token.create.ts +++ b/packages/functions/src/controls/token/token.create.ts @@ -9,7 +9,8 @@ import { WenError, } from '@build-5/interfaces'; import { merge } from 'lodash'; -import { hasStakedSoonTokens } from '../../services/stake.service'; +import { Context } from '../../runtime/firebase/common'; +import { hasStakedTokens } from '../../services/stake.service'; import { assertSpaceHasValidAddress } from '../../utils/address.utils'; import { isProdEnv } from '../../utils/config.utils'; import { dateToTimestamp } from '../../utils/dateTime.utils'; @@ -18,8 +19,11 @@ import { assertIsGuardian } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; import { getPublicSaleTimeFrames, shouldSetPublicSaleTimeFrames } from './common'; -export const createTokenControl = async (owner: string, params: TokenCreateRequest) => { - const hasStakedSoons = await hasStakedSoonTokens(owner); +export const createTokenControl = async ( + { project, owner }: Context, + params: TokenCreateRequest, +) => { + const hasStakedSoons = await hasStakedTokens(project, owner); if (!hasStakedSoons) { throw invalidArgument(WenError.no_staked_soon); } diff --git a/packages/functions/src/controls/token/token.credit.ts b/packages/functions/src/controls/token/token.credit.ts index 9092f1f896..05a789e1ea 100644 --- a/packages/functions/src/controls/token/token.credit.ts +++ b/packages/functions/src/controls/token/token.credit.ts @@ -14,6 +14,7 @@ import { TransactionType, WenError, } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { getAddress } from '../../utils/address.utils'; import { invalidArgument } from '../../utils/error.utils'; import { @@ -25,7 +26,7 @@ import { import { getRandomEthAddress } from '../../utils/wallet.utils'; export const creditTokenControl = async ( - owner: string, + { owner }: Context, params: CreditTokenRequest, ): Promise => { const tranId = getRandomEthAddress(); diff --git a/packages/functions/src/controls/token/token.enable.trading.ts b/packages/functions/src/controls/token/token.enable.trading.ts index c2bdc1b41d..2cfa547b90 100644 --- a/packages/functions/src/controls/token/token.enable.trading.ts +++ b/packages/functions/src/controls/token/token.enable.trading.ts @@ -1,10 +1,11 @@ import { build5Db } from '@build-5/database'; import { COL, EnableTokenTradingRequest, Token, WenError } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian } from '../../utils/token.utils'; export const enableTokenTradingControl = async ( - owner: string, + { owner }: Context, params: EnableTokenTradingRequest, ) => { const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${params.uid}`); diff --git a/packages/functions/src/controls/token/token.order.ts b/packages/functions/src/controls/token/token.order.ts index 27988ab0ab..f3ef0dd996 100644 --- a/packages/functions/src/controls/token/token.order.ts +++ b/packages/functions/src/controls/token/token.order.ts @@ -1,4 +1,3 @@ -import { build5Db } from '@build-5/database'; import { COL, DEFAULT_NETWORK, @@ -15,6 +14,7 @@ import { WenError, } from '@build-5/interfaces'; import dayjs from 'dayjs'; +import { Context } from '../../runtime/firebase/common'; import { assertHasAccess } from '../../services/validators/access'; import { WalletService } from '../../services/wallet/wallet'; import { assertMemberHasValidAddress, getAddress } from '../../utils/address.utils'; @@ -23,12 +23,9 @@ import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { assertIpNotBlocked } from '../../utils/ip.utils'; import { tokenIsInPublicSalePeriod, tokenOrderTransactionDocId } from '../../utils/token.utils'; +import { build5Db } from '@build-5/database'; -export const orderTokenControl = async ( - owner: string, - params: OrderTokenRequest, - customParams?: Record, -) => { +export const orderTokenControl = async ({ owner, ip }: Context, params: OrderTokenRequest) => { const memberDocRef = build5Db().doc(`${COL.MEMBER}/${owner}`); const member = await memberDocRef.get(); assertMemberHasValidAddress(member, DEFAULT_NETWORK); @@ -39,7 +36,7 @@ export const orderTokenControl = async ( } if (isProdEnv()) { - await assertIpNotBlocked((customParams?.ip as string) || '', token.uid, 'token'); + await assertIpNotBlocked(ip, token.uid, 'token'); } if (!tokenIsInPublicSalePeriod(token) || token.status !== TokenStatus.AVAILABLE) { diff --git a/packages/functions/src/controls/token/token.set.for.sale.ts b/packages/functions/src/controls/token/token.set.for.sale.ts index eed4f43105..716c8ad0f4 100644 --- a/packages/functions/src/controls/token/token.set.for.sale.ts +++ b/packages/functions/src/controls/token/token.set.for.sale.ts @@ -1,12 +1,13 @@ -import { build5Db } from '@build-5/database'; import { COL, SetTokenForSaleRequest, Token, TokenStatus, WenError } from '@build-5/interfaces'; +import { Context } from '../../runtime/firebase/common'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { invalidArgument } from '../../utils/error.utils'; import { assertIsGuardian, assertTokenApproved, assertTokenStatus } from '../../utils/token.utils'; import { getPublicSaleTimeFrames, shouldSetPublicSaleTimeFrames } from './common'; +import { build5Db } from '@build-5/database'; export const setTokenAvailableForSaleControl = async ( - owner: string, + { owner }: Context, params: SetTokenForSaleRequest, ) => { const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${params.token}`); diff --git a/packages/functions/src/controls/token/token.update.ts b/packages/functions/src/controls/token/token.update.ts index 10ec72db18..643406af39 100644 --- a/packages/functions/src/controls/token/token.update.ts +++ b/packages/functions/src/controls/token/token.update.ts @@ -1,6 +1,5 @@ -import { build5Db } from '@build-5/database'; import { COL, Token, TokenStatus, WenError } from '@build-5/interfaces'; -import { UidSchemaObject } from '../../runtime/firebase/common'; +import { Context, UidSchemaObject } from '../../runtime/firebase/common'; import { updateTokenSchemaObject, uptdateMintedTokenSchemaObject, @@ -8,8 +7,9 @@ import { import { invalidArgument } from '../../utils/error.utils'; import { assertValidationAsync } from '../../utils/schema.utils'; import { assertIsGuardian, assertTokenStatus } from '../../utils/token.utils'; +import { build5Db } from '@build-5/database'; -export const updateTokenControl = async (owner: string, params: UidSchemaObject) => { +export const updateTokenControl = async ({ owner }: Context, params: UidSchemaObject) => { const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${params.uid}`); await build5Db().runTransaction(async (transaction) => { const token = await transaction.get(tokenDocRef); diff --git a/packages/functions/src/controls/vote.control.ts b/packages/functions/src/controls/vote.control.ts index 7b8086f185..25c7601f2e 100644 --- a/packages/functions/src/controls/vote.control.ts +++ b/packages/functions/src/controls/vote.control.ts @@ -1,10 +1,11 @@ import { build5Db } from '@build-5/database'; import { COL, Collection, SUB_COL, Token, Vote, VoteRequest, WenError } from '@build-5/interfaces'; -import { hasStakedSoonTokens } from '../services/stake.service'; +import { Context } from '../runtime/firebase/common'; +import { hasStakedTokens } from '../services/stake.service'; import { invalidArgument } from '../utils/error.utils'; -export const voteControl = async (owner: string, params: VoteRequest) => { - const hasStakedSoons = await hasStakedSoonTokens(owner); +export const voteControl = async ({ project, owner }: Context, params: VoteRequest) => { + const hasStakedSoons = await hasStakedTokens(project, owner); if (!hasStakedSoons) { throw invalidArgument(WenError.no_staked_soon); } diff --git a/packages/functions/src/cron/nft.cron.ts b/packages/functions/src/cron/nft.cron.ts index 1daed5d0e7..ae4b99edd6 100644 --- a/packages/functions/src/cron/nft.cron.ts +++ b/packages/functions/src/cron/nft.cron.ts @@ -1,15 +1,18 @@ import { build5Db } from '@build-5/database'; import { COL, Nft } from '@build-5/interfaces'; import dayjs from 'dayjs'; -import { ProcessingService } from '../services/payment/payment-processing'; +import { NftBidService } from '../services/payment/nft/nft-bid.service'; +import { TransactionService } from '../services/payment/transaction-service'; const finalizeNftAuction = (nftId: string) => build5Db().runTransaction(async (transaction) => { const nftDocRef = build5Db().collection(COL.NFT).doc(nftId); const nft = (await transaction.get(nftDocRef))!; - const service = new ProcessingService(transaction); + + const tranService = new TransactionService(transaction); + const service = new NftBidService(tranService); await service.markNftAsFinalized(nft); - service.submit(); + tranService.submit(); }); export const finalizeAllNftAuctions = async () => { diff --git a/packages/functions/src/cron/orders.cron.ts b/packages/functions/src/cron/orders.cron.ts index 6c1f6fb3dc..30dbe495c7 100644 --- a/packages/functions/src/cron/orders.cron.ts +++ b/packages/functions/src/cron/orders.cron.ts @@ -1,7 +1,8 @@ import { build5Db } from '@build-5/database'; import { COL, Transaction, TransactionType } from '@build-5/interfaces'; import dayjs from 'dayjs'; -import { ProcessingService } from '../services/payment/payment-processing'; +import { NftPurchaseService } from '../services/payment/nft/nft-purchase.service'; +import { TransactionService } from '../services/payment/transaction-service'; export const voidExpiredOrdersCron = async () => { const snap = await build5Db() @@ -16,9 +17,11 @@ export const voidExpiredOrdersCron = async () => { await build5Db().runTransaction(async (transaction) => { const tranDocRef = build5Db().doc(`${COL.TRANSACTION}/${tran.uid}`); const tranData = (await transaction.get(tranDocRef))!; - const service: ProcessingService = new ProcessingService(transaction); + + const tranService = new TransactionService(transaction); + const service = new NftPurchaseService(tranService); await service.markAsVoid(tranData); - service.submit(); + tranService.submit(); }); } diff --git a/packages/functions/src/index.ts b/packages/functions/src/index.ts index 92a09eedcc..d2478ae157 100644 --- a/packages/functions/src/index.ts +++ b/packages/functions/src/index.ts @@ -1,5 +1,6 @@ import { WEN_FUNC, WEN_FUNC_TRIGGER } from '@build-5/interfaces'; import { algoliaTrigger } from './algolia/algolia.trigger'; +import { initSoonProject } from './controls/project/init.soon.project'; import { validateAddress } from './runtime/firebase/address'; import { generateCustomToken } from './runtime/firebase/auth'; import { @@ -31,6 +32,7 @@ import { updateUnsoldNft, withdrawNft, } from './runtime/firebase/nft/index'; +import { createProject, deactivateProject } from './runtime/firebase/project'; import { approveProposal, createProposal, @@ -52,6 +54,7 @@ import { updateSpace, } from './runtime/firebase/space'; import { depositStake, removeStakeReward, stakeReward } from './runtime/firebase/stake'; +import { uploadFile } from './runtime/firebase/storage/file.upload'; import { airdropToken, cancelPublicSale, @@ -91,7 +94,6 @@ import { onTokenTradeOrderWrite } from './triggers/token-trading/token-trade-ord import { onTokenStatusUpdate } from './triggers/token.trigger'; import { transactionWrite } from './triggers/transaction-trigger/transaction.trigger'; import { isProdEnv } from './utils/config.utils'; -import { uploadFile } from './runtime/firebase/storage/file.upload'; // Members functions. exports[WEN_FUNC.createMember] = createMember; @@ -204,3 +206,9 @@ exports[WEN_FUNC.claimSpace] = claimSpace; exports[WEN_FUNC.importMintedToken] = importMintedToken; exports[WEN_FUNC.uploadFile] = uploadFile; + +exports[WEN_FUNC.createProject] = createProject; +exports[WEN_FUNC.deactivateProject] = deactivateProject; + +// TODO Remove this after release +exports['init_soon_project'] = initSoonProject; diff --git a/packages/functions/src/runtime/firebase/auth/CutomTokenRequestSchema.ts b/packages/functions/src/runtime/firebase/auth/CutomTokenRequestSchema.ts index 37afe378d3..44cba21b8e 100644 --- a/packages/functions/src/runtime/firebase/auth/CutomTokenRequestSchema.ts +++ b/packages/functions/src/runtime/firebase/auth/CutomTokenRequestSchema.ts @@ -1,5 +1,6 @@ +import { CustomTokenRequest } from '@build-5/interfaces'; import Joi from 'joi'; -export const customTokenSchema = Joi.object({}) +export const customTokenSchema = Joi.object({}) .description('Request object to create a custom login token. No params required.') .meta({ className: 'CustomTokenRequest' }); diff --git a/packages/functions/src/runtime/firebase/auth/index.ts b/packages/functions/src/runtime/firebase/auth/index.ts index 3ab516f5ba..4767832dc6 100644 --- a/packages/functions/src/runtime/firebase/auth/index.ts +++ b/packages/functions/src/runtime/firebase/auth/index.ts @@ -1,7 +1,7 @@ import { WEN_FUNC } from '@build-5/interfaces'; import { generateCustomTokenControl } from '../../../controls/auth.control'; -import { customTokenSchema } from './CutomTokenRequestSchema'; import { onRequest } from '../common'; +import { customTokenSchema } from './CutomTokenRequestSchema'; export const generateCustomToken = onRequest(WEN_FUNC.generateCustomToken, undefined, { allowUnknown: true, diff --git a/packages/functions/src/runtime/firebase/common.ts b/packages/functions/src/runtime/firebase/common.ts index 83b9b3c854..616610f09a 100644 --- a/packages/functions/src/runtime/firebase/common.ts +++ b/packages/functions/src/runtime/firebase/common.ts @@ -15,6 +15,12 @@ export interface UidSchemaObject { } export const uidSchema = { uid: CommonJoi.uid() }; +export interface Context { + ip: string; + owner: string; + project: string; +} + export const onRequest = ( funcName: WEN_FUNC, @@ -23,20 +29,22 @@ export const onRequest = ) => ( schema: Joi.AnySchema

, - func: (owner: string, params: P, customParams?: Record) => Promise, + func: (context: Context, params: P) => Promise, validateOnlyUid = false, + skipProject = false, ) => functions.https.onRequest(onRequestConfig(funcName, runtimeOptions), (req, res) => cors({ origin: true })(req, res, async () => { try { - const params = await decodeAuth(req.body.data, funcName); + const params = await decodeAuth(req.body.data, funcName, skipProject); const owner = params.address.toLowerCase(); await assertValidationAsync( schema, validateOnlyUid ? { uid: params.body.uid } : params.body, joiOptions, ); - const result = await func(owner, params.body, { ip: req.ip }); + const context = { ip: req.ip || '', owner, project: params.project }; + const result = await func(context, params.body); res.send({ data: result || {} }); } catch (error) { res.status(get(error, 'httpErrorCode.status', 400)); diff --git a/packages/functions/src/runtime/firebase/member/index.ts b/packages/functions/src/runtime/firebase/member/index.ts index 7220092490..ad195df392 100644 --- a/packages/functions/src/runtime/firebase/member/index.ts +++ b/packages/functions/src/runtime/firebase/member/index.ts @@ -13,10 +13,10 @@ export const createMember = functions.https.onRequest( (req, res) => cors({ origin: true })(req, res, async () => { try { - const address = req.body.data; + const address = req.body.data as string; await assertValidationAsync(createMemberSchema, { address }); - res.send({ data: await createMemberControl(address) }); - } catch { + res.send({ data: await createMemberControl({ owner: address, ip: '', project: '' }) }); + } catch (error) { res.status(401); res.send({ data: WenError.address_must_be_provided }); } diff --git a/packages/functions/src/runtime/firebase/project/ProjectCreateSchema.ts b/packages/functions/src/runtime/firebase/project/ProjectCreateSchema.ts new file mode 100644 index 0000000000..4385c8febd --- /dev/null +++ b/packages/functions/src/runtime/firebase/project/ProjectCreateSchema.ts @@ -0,0 +1,64 @@ +import { ProjectBilling } from '@build-5/interfaces'; +import Joi from 'joi'; +import { CommonJoi, toJoiObject } from '../../../services/joi/common'; + +const MIN_PROJECT_NAME_LENGTH = 3; +const MAX_PROJECT_NAME_LENGTH = 40; +export const projectCreateSchema = toJoiObject({ + name: Joi.string() + .min(MIN_PROJECT_NAME_LENGTH) + .max(MAX_PROJECT_NAME_LENGTH) + .required() + .description( + `Name of the project. Minimum ${MIN_PROJECT_NAME_LENGTH}, maximum ${MAX_PROJECT_NAME_LENGTH} character`, + ), + contactEmail: Joi.string().email().description('Email address of a contact for the project.'), + config: Joi.object({ + billing: Joi.string() + .valid(...Object.values(ProjectBilling)) + .required() + .description('Billing type of the project.'), + tiers: Joi.array() + .when('billing', { + is: Joi.exist().valid(ProjectBilling.TOKEN_BASE), + then: Joi.array().items(Joi.number().integer().min(0)).min(5).max(5).required(), + otherwise: Joi.forbidden(), + }) + .description( + `Tiers for this project. Set only if billing type is ${ProjectBilling.TOKEN_BASE}`, + ), + tokenTradingFeeDiscountPercentage: Joi.array() + .when('billing', { + is: Joi.exist().valid(ProjectBilling.TOKEN_BASE), + then: Joi.array().items(Joi.number().integer().min(0)).min(5).max(5).required(), + otherwise: Joi.forbidden(), + }) + .description( + `Discounts for this project. Set only if billing type is ${ProjectBilling.TOKEN_BASE}`, + ), + baseTokenSymbol: Joi.string() + .when('billing', { + is: Joi.exist().valid(ProjectBilling.TOKEN_BASE), + then: CommonJoi.tokenSymbol(), + otherwise: Joi.forbidden(), + }) + .description( + `Base token symbol for this project. Set only if billing type is ${ProjectBilling.TOKEN_BASE}`, + ), + baseTokenUid: Joi.string() + .when('billing', { + is: Joi.exist().valid(ProjectBilling.TOKEN_BASE), + then: CommonJoi.uid(), + otherwise: Joi.forbidden(), + }) + .description( + `Base token uid for this project. Set only if billing type is ${ProjectBilling.TOKEN_BASE}`, + ), + }) + .required() + .description('Config for this project.'), +}) + .description('Request object to create a project.') + .meta({ + className: 'ProjectCreateRequest', + }); diff --git a/packages/functions/src/runtime/firebase/project/ProjectDeactivateSchema.ts b/packages/functions/src/runtime/firebase/project/ProjectDeactivateSchema.ts new file mode 100644 index 0000000000..3ec04c5a0a --- /dev/null +++ b/packages/functions/src/runtime/firebase/project/ProjectDeactivateSchema.ts @@ -0,0 +1,10 @@ +import { ProjectDeactivateRequest } from '@build-5/interfaces'; +import { CommonJoi, toJoiObject } from '../../../services/joi/common'; + +export const projectDeactivateSchema = toJoiObject({ + project: CommonJoi.uid(), +}) + .description('Request object to deactivate a project.') + .meta({ + className: 'ProjectDeactivateRequest', + }); diff --git a/packages/functions/src/runtime/firebase/project/index.ts b/packages/functions/src/runtime/firebase/project/index.ts new file mode 100644 index 0000000000..2568642455 --- /dev/null +++ b/packages/functions/src/runtime/firebase/project/index.ts @@ -0,0 +1,18 @@ +import { WEN_FUNC } from '@build-5/interfaces'; +import { createProjectControl } from '../../../controls/project/project.create.control'; +import { deactivateProjectControl } from '../../../controls/project/project.deactivate.control'; +import { onRequest } from '../common'; +import { projectCreateSchema } from './ProjectCreateSchema'; +import { projectDeactivateSchema } from './ProjectDeactivateSchema'; + +export const createProject = onRequest(WEN_FUNC.createProject)( + projectCreateSchema, + createProjectControl, + undefined, + false, +); + +export const deactivateProject = onRequest(WEN_FUNC.deactivateProject)( + projectDeactivateSchema, + deactivateProjectControl, +); diff --git a/packages/functions/src/services/payment/address/address-member.service.ts b/packages/functions/src/services/payment/address/address-member.service.ts new file mode 100644 index 0000000000..42f729580b --- /dev/null +++ b/packages/functions/src/services/payment/address/address-member.service.ts @@ -0,0 +1,71 @@ +import { build5Db, getSnapshot } from '@build-5/database'; +import { + COL, + DEFAULT_NETWORK, + Entity, + Network, + NetworkAddress, + Transaction, + TransactionPayloadType, + TransactionType, +} from '@build-5/interfaces'; +import { last } from 'lodash'; +import { HandlerParams } from '../base'; +import { BaseAddressService } from './common'; + +export class MemberAddressService extends BaseAddressService { + public handleRequest = async ({ order, match }: HandlerParams) => { + const payment = await this.transactionService.createPayment(order, match); + const credit = await this.transactionService.createCredit( + TransactionPayloadType.ADDRESS_VALIDATION, + payment, + match, + ); + if (credit) { + await this.setValidatedAddress(credit, Entity.MEMBER); + await claimBadges( + order.member!, + credit.payload.targetAddress!, + order.network || DEFAULT_NETWORK, + ); + } + this.transactionService.markAsReconciled(order, match.msgId); + }; +} + +const claimBadges = async (member: string, memberAddress: NetworkAddress, network: Network) => { + let lastDocId = ''; + do { + const lastDoc = await getSnapshot(COL.TRANSACTION, lastDocId); + const snap = await build5Db() + .collection(COL.TRANSACTION) + .where('network', '==', network) + .where('type', '==', TransactionType.AWARD) + .where('member', '==', member) + .where('ignoreWallet', '==', true) + .where('payload.type', '==', TransactionPayloadType.BADGE) + .limit(500) + .startAfter(lastDoc) + .get(); + lastDocId = last(snap)?.uid || ''; + + const promises = snap.map((badgeTransaction) => + updateBadgeTransaction(badgeTransaction.uid, memberAddress), + ); + await Promise.all(promises); + } while (lastDocId); +}; + +const updateBadgeTransaction = async (transactionId: string, memberAddress: NetworkAddress) => + build5Db().runTransaction(async (transaction) => { + const badgeDocRef = build5Db().doc(`${COL.TRANSACTION}/${transactionId}`); + const badge = await transaction.get(badgeDocRef); + if (badge?.ignoreWallet) { + const data = { + ignoreWallet: false, + 'payload.targetAddress': memberAddress, + shouldRetry: true, + }; + transaction.update(badgeDocRef, data); + } + }); diff --git a/packages/functions/src/services/payment/address-service.ts b/packages/functions/src/services/payment/address/address.space.service.ts similarity index 55% rename from packages/functions/src/services/payment/address-service.ts rename to packages/functions/src/services/payment/address/address.space.service.ts index a202fb5533..0bedd1833b 100644 --- a/packages/functions/src/services/payment/address-service.ts +++ b/packages/functions/src/services/payment/address/address.space.service.ts @@ -1,12 +1,9 @@ -import { build5Db, getSnapshot } from '@build-5/database'; +import { build5Db } from '@build-5/database'; import { BaseProposalAnswerValue, COL, DEFAULT_NETWORK, - Entity, Member, - Network, - NetworkAddress, Proposal, ProposalType, SUB_COL, @@ -18,40 +15,13 @@ import { UPDATE_SPACE_THRESHOLD_PERCENTAGE, } from '@build-5/interfaces'; import dayjs from 'dayjs'; -import { last } from 'lodash'; -import { getAddress } from '../../utils/address.utils'; -import { dateToTimestamp } from '../../utils/dateTime.utils'; -import { getRandomEthAddress } from '../../utils/wallet.utils'; -import { TransactionMatch, TransactionService } from './transaction-service'; +import { getAddress } from '../../../utils/address.utils'; +import { dateToTimestamp } from '../../../utils/dateTime.utils'; +import { getRandomEthAddress } from '../../../utils/wallet.utils'; +import { BaseService, HandlerParams } from '../base'; -export class AddressService { - constructor(readonly transactionService: TransactionService) {} - - public async handleAddressValidationRequest( - order: Transaction, - match: TransactionMatch, - type: Entity, - ) { - const payment = await this.transactionService.createPayment(order, match); - const credit = await this.transactionService.createCredit( - TransactionPayloadType.ADDRESS_VALIDATION, - payment, - match, - ); - if (credit) { - await this.setValidatedAddress(credit, type); - if (type === Entity.MEMBER) { - await claimBadges( - order.member!, - credit.payload.targetAddress!, - order.network || DEFAULT_NETWORK, - ); - } - } - this.transactionService.markAsReconciled(order, match.msgId); - } - - public async handleSpaceAddressValidationRequest(order: Transaction, match: TransactionMatch) { +export class SpaceAddressService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { const payment = await this.transactionService.createPayment(order, match); await this.transactionService.createCredit( TransactionPayloadType.ADDRESS_VALIDATION, @@ -119,63 +89,12 @@ export class AddressService { data: proposal, action: 'set', }); - } - - private async setValidatedAddress(credit: Transaction, type: Entity): Promise { - const collection = type === Entity.MEMBER ? COL.MEMBER : COL.SPACE; - const id = type === Entity.MEMBER ? credit.member : credit.space; - const ref = build5Db().doc(`${collection}/${id}`); - const docData = await ref.get>(); - const network = credit.network || DEFAULT_NETWORK; - const currentAddress = getAddress(docData, network); - const data = { [`validatedAddress.${network}`]: credit.payload.targetAddress }; - if (currentAddress) { - data.prevValidatedAddresses = build5Db().arrayUnion(currentAddress); - } - this.transactionService.push({ ref, data, action: 'update' }); - } + }; } -const claimBadges = async (member: string, memberAddress: NetworkAddress, network: Network) => { - let lastDocId = ''; - do { - const lastDoc = await getSnapshot(COL.TRANSACTION, lastDocId); - const snap = await build5Db() - .collection(COL.TRANSACTION) - .where('network', '==', network) - .where('type', '==', TransactionType.AWARD) - .where('member', '==', member) - .where('ignoreWallet', '==', true) - .where('payload.type', '==', TransactionPayloadType.BADGE) - .limit(500) - .startAfter(lastDoc) - .get(); - lastDocId = last(snap)?.uid || ''; - - const promises = snap.map((badgeTransaction) => - updateBadgeTransaction(badgeTransaction.uid, memberAddress), - ); - await Promise.all(promises); - } while (lastDocId); -}; - -const updateBadgeTransaction = async (transactionId: string, memberAddress: NetworkAddress) => - build5Db().runTransaction(async (transaction) => { - const badgeDocRef = build5Db().doc(`${COL.TRANSACTION}/${transactionId}`); - const badge = await transaction.get(badgeDocRef); - if (badge?.ignoreWallet) { - const data = { - ignoreWallet: false, - 'payload.targetAddress': memberAddress, - shouldRetry: true, - }; - transaction.update(badgeDocRef, data); - } - }); - const createUpdateSpaceValidatedAddressProposal = ( order: Transaction, - validatedAddress: NetworkAddress, + validatedAddress: string, owner: Member, space: Space, guardiansCount: number, diff --git a/packages/functions/src/services/payment/address/common.ts b/packages/functions/src/services/payment/address/common.ts new file mode 100644 index 0000000000..72138fc1f5 --- /dev/null +++ b/packages/functions/src/services/payment/address/common.ts @@ -0,0 +1,20 @@ +import { build5Db } from '@build-5/database'; +import { COL, DEFAULT_NETWORK, Entity, Transaction } from '@build-5/interfaces'; +import { getAddress } from '../../../utils/address.utils'; +import { BaseService } from '../base'; + +export abstract class BaseAddressService extends BaseService { + protected async setValidatedAddress(credit: Transaction, type: Entity): Promise { + const collection = type === Entity.MEMBER ? COL.MEMBER : COL.SPACE; + const id = type === Entity.MEMBER ? credit.member : credit.space; + const ref = build5Db().doc(`${collection}/${id}`); + const docData = await ref.get>(); + const network = credit.network || DEFAULT_NETWORK; + const currentAddress = getAddress(docData, network); + const data = { [`validatedAddress.${network}`]: credit.payload.targetAddress }; + if (currentAddress) { + data.prevValidatedAddresses = build5Db().arrayUnion(currentAddress); + } + this.transactionService.push({ ref, data, action: 'update' }); + } +} diff --git a/packages/functions/src/services/payment/award/award-service.ts b/packages/functions/src/services/payment/award/award-service.ts index 5970000ce7..e6deb14e4f 100644 --- a/packages/functions/src/services/payment/award/award-service.ts +++ b/packages/functions/src/services/payment/award/award-service.ts @@ -29,12 +29,10 @@ import { getContentType } from '../../../utils/storage.utils'; import { createAliasOutput } from '../../../utils/token-minting-utils/alias.utils'; import { getRandomEthAddress } from '../../../utils/wallet.utils'; import { SmrWallet } from '../../wallet/SmrWalletService'; -import { TransactionMatch, TransactionService } from '../transaction-service'; +import { BaseService, HandlerParams } from '../base'; -export class AwardService { - constructor(readonly transactionService: TransactionService) {} - - public handleAwardFundingOrder = async (order: Transaction, match: TransactionMatch) => { +export class AwardService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { const payment = await this.transactionService.createPayment(order, match); const awardDocRef = build5Db().doc(`${COL.AWARD}/${order.payload.award}`); diff --git a/packages/functions/src/services/payment/base.ts b/packages/functions/src/services/payment/base.ts new file mode 100644 index 0000000000..b263bdc9e6 --- /dev/null +++ b/packages/functions/src/services/payment/base.ts @@ -0,0 +1,32 @@ +import { ITransaction } from '@build-5/database'; +import { + MilestoneTransaction, + MilestoneTransactionEntry, + Network, + Transaction, +} from '@build-5/interfaces'; +import { TransactionMatch, TransactionService } from './transaction-service'; + +export interface HandlerParams { + network: Network; + payment: Transaction | undefined; + match: TransactionMatch; + order: Transaction; + owner: string; + tran: MilestoneTransaction; + orderId: string; + build5Tran: Transaction | undefined; + tangleOrder: Transaction; + tranEntry: MilestoneTransactionEntry; + request: Record; +} + +export abstract class BaseService { + protected transaction: ITransaction; + + constructor(readonly transactionService: TransactionService) { + this.transaction = transactionService.transaction; + } + + abstract handleRequest(params: HandlerParams): Promise; +} diff --git a/packages/functions/src/services/payment/credit-service.ts b/packages/functions/src/services/payment/credit-service.ts index c95572bbbf..3b08537c82 100644 --- a/packages/functions/src/services/payment/credit-service.ts +++ b/packages/functions/src/services/payment/credit-service.ts @@ -2,12 +2,10 @@ import { build5Db } from '@build-5/database'; import { COL, Transaction, TransactionPayloadType, TransactionType } from '@build-5/interfaces'; import { get, isEmpty } from 'lodash'; import { getRandomEthAddress } from '../../utils/wallet.utils'; -import { TransactionMatch, TransactionService } from './transaction-service'; +import { BaseService, HandlerParams } from './base'; -export class CreditService { - constructor(readonly transactionService: TransactionService) {} - - public handleCreditUnrefundableOrder = async (order: Transaction, match: TransactionMatch) => { +export class CreditService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { const payment = await this.transactionService.createPayment(order, match); const transactionDocRef = build5Db().doc( diff --git a/packages/functions/src/services/payment/metadataNft-service.ts b/packages/functions/src/services/payment/metadataNft-service.ts index 9f0a9fea73..53639834ee 100644 --- a/packages/functions/src/services/payment/metadataNft-service.ts +++ b/packages/functions/src/services/payment/metadataNft-service.ts @@ -21,12 +21,10 @@ import { getNftByMintingId, } from '../../utils/collection-minting-utils/nft.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; -import { TransactionMatch, TransactionService } from './transaction-service'; +import { BaseService, HandlerParams } from './base'; -export class MetadataNftService { - constructor(readonly transactionService: TransactionService) {} - - public async handleMintMetadataNftRequest(order: Transaction, match: TransactionMatch) { +export class MetadataNftService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { const payment = await this.transactionService.createPayment(order, match); this.transactionService.markAsReconciled(order, match.msgId); @@ -108,7 +106,7 @@ export class MetadataNftService { const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${mintNftOrder.uid}`); this.transactionService.push({ ref: orderDocRef, data: mintNftOrder, action: 'set' }); return; - } + }; } export const createMetadataNft = ( diff --git a/packages/functions/src/services/payment/nft/collection-minting-service.ts b/packages/functions/src/services/payment/nft/collection-minting.service.ts similarity index 85% rename from packages/functions/src/services/payment/nft/collection-minting-service.ts rename to packages/functions/src/services/payment/nft/collection-minting.service.ts index bf507589c8..1dbb19877f 100644 --- a/packages/functions/src/services/payment/nft/collection-minting-service.ts +++ b/packages/functions/src/services/payment/nft/collection-minting.service.ts @@ -3,17 +3,14 @@ import { COL, Collection, CollectionStatus, - Transaction, TransactionPayloadType, UnsoldMintingOptions, } from '@build-5/interfaces'; import { get } from 'lodash'; -import { TransactionMatch, TransactionService } from '../transaction-service'; +import { BaseService, HandlerParams } from '../base'; -export class CollectionMintingService { - constructor(readonly transactionService: TransactionService) {} - - public handleCollectionMintingRequest = async (order: Transaction, match: TransactionMatch) => { +export class CollectionMintingService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { const collectionDocRef = build5Db().doc(`${COL.COLLECTION}/${order.payload.collection}`); const collection = await this.transactionService.get(collectionDocRef); diff --git a/packages/functions/src/services/payment/nft/nft-service.ts b/packages/functions/src/services/payment/nft/common.ts similarity index 61% rename from packages/functions/src/services/payment/nft/nft-service.ts rename to packages/functions/src/services/payment/nft/common.ts index 2aa7491c83..8851ad9149 100644 --- a/packages/functions/src/services/payment/nft/nft-service.ts +++ b/packages/functions/src/services/payment/nft/common.ts @@ -3,8 +3,6 @@ import { COL, Collection, Member, - MilestoneTransaction, - MilestoneTransactionEntry, Nft, NftAccess, Transaction, @@ -12,133 +10,23 @@ import { } from '@build-5/interfaces'; import dayjs from 'dayjs'; import { last, set } from 'lodash'; -import { AVAILABLE_NETWORKS } from '../../../controls/common'; import { getAddress } from '../../../utils/address.utils'; import { dateToTimestamp, serverTime } from '../../../utils/dateTime.utils'; import { NotificationService } from '../../notification/notification'; +import { BaseService } from '../base'; import { createNftWithdrawOrder } from '../tangle-service/nft/nft-purchase.service'; -import { TransactionMatch, TransactionService } from '../transaction-service'; -export class NftService { - constructor(readonly transactionService: TransactionService) {} - - public async handleNftPurchaseRequest( - tran: MilestoneTransaction, - tranOutput: MilestoneTransactionEntry, - order: Transaction, - match: TransactionMatch, - build5Transaction: Transaction | undefined, - ) { - const nftDocRef = build5Db().doc(`${COL.NFT}/${order.payload.nft}`); - const nft = await this.transactionService.get(nftDocRef); - - if (nft.availableFrom === null) { - await this.transactionService.processAsInvalid(tran, order, tranOutput, build5Transaction); - return; - } - - const payment = await this.transactionService.createPayment(order, match); - this.transactionService.createBillPayment(order, payment); - await this.setNftOwner(order, payment); - this.transactionService.markAsReconciled(order, match.msgId); - - this.setTradingStats(nft); - - const tanglePuchase = order.payload.tanglePuchase; - const disableWithdraw = order.payload.disableWithdraw; - if (!disableWithdraw && tanglePuchase && AVAILABLE_NETWORKS.includes(order.network!)) { - await this.withdrawNft(order, nft); - } - } - - public async handleNftBidRequest( - tran: MilestoneTransaction, - tranOutput: MilestoneTransactionEntry, - order: Transaction, - match: TransactionMatch, - build5Transaction: Transaction | undefined, - ) { - const nftDocRef = build5Db().collection(COL.NFT).doc(order.payload.nft!); - const nft = await this.transactionService.get(nftDocRef); - if (nft?.auctionFrom) { - const payment = await this.transactionService.createPayment(order, match); - await this.addNewBid(order, payment); - } else { - await this.transactionService.processAsInvalid(tran, order, tranOutput, build5Transaction); - } - } - - public async markNftAsFinalized({ uid, auctionFrom }: Nft): Promise { - if (!auctionFrom) { - throw new Error('NFT auctionFrom is no longer defined'); - } - - const nftDocRef = build5Db().doc(`${COL.NFT}/${uid}`); - const nft = await this.transactionService.get(nftDocRef); - if (nft.auctionHighestTransaction) { - const highestPayDocRef = build5Db().doc( - `${COL.TRANSACTION}/${nft.auctionHighestTransaction}`, - ); - const highestPay = (await highestPayDocRef.get())!; - - const orderId = Array.isArray(highestPay.payload.sourceTransaction) - ? last(highestPay.payload.sourceTransaction)! - : highestPay.payload.sourceTransaction!; - const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${orderId}`); - const order = await orderDocRef.get(); - if (!order) { - throw new Error('Unable to find ORDER linked to PAYMENT'); - } - - this.transactionService.markAsReconciled(order, highestPay.payload.chainReference!); - this.transactionService.createBillPayment(order, highestPay); - await this.setNftOwner(order, highestPay); - - const memberDocRef = build5Db().collection(COL.MEMBER).doc(order.member!); - const member = await memberDocRef.get(); - - const notification = NotificationService.prepareWinBid(member, nft, highestPay); - const notificationDocRef = build5Db().doc(`${COL.NOTIFICATION}/${notification.uid}`); - this.transactionService.push({ - ref: notificationDocRef, - data: notification, - action: 'set', - }); - this.transactionService.push({ - ref: orderDocRef, - data: { - linkedTransactions: build5Db().arrayUnion(...this.transactionService.linkedTransactions), - }, - action: 'update', - }); - - this.setTradingStats(nft); +export abstract class BaseNftService extends BaseService { + protected setTradingStats = (nft: Nft) => { + const data = { lastTradedOn: serverTime(), totalTrades: build5Db().inc(1) }; + const collectionDocRef = build5Db().doc(`${COL.COLLECTION}/${nft.collection}`); + this.transactionService.push({ ref: collectionDocRef, data, action: 'update' }); - const tanglePuchase = order.payload.tanglePuchase; - const disableWithdraw = order.payload.disableWithdraw; - if (!disableWithdraw && tanglePuchase && AVAILABLE_NETWORKS.includes(order.network!)) { - await this.withdrawNft(order, nft); - } - } else { - this.transactionService.push({ - ref: nftDocRef, - data: { - auctionFrom: null, - auctionTo: null, - extendedAuctionTo: null, - auctionFloorPrice: null, - auctionLength: null, - extendedAuctionLength: null, - auctionHighestBid: null, - auctionHighestBidder: null, - auctionHighestTransaction: null, - }, - action: 'update', - }); - } - } + const nftDocRef = build5Db().doc(`${COL.NFT}/${nft.uid}`); + this.transactionService.push({ ref: nftDocRef, data, action: 'update' }); + }; - private withdrawNft = async (order: Transaction, nft: Nft) => { + protected withdrawNft = async (order: Transaction, nft: Nft) => { const membderDocRef = build5Db().doc(`${COL.MEMBER}/${order.member}`); const member = await membderDocRef.get(); const { order: withdrawOrder, nftUpdateData } = createNftWithdrawOrder( @@ -158,43 +46,113 @@ export class NftService { }); }; - public async markAsVoid(transaction: Transaction): Promise { - const refSource = build5Db().doc(`${COL.TRANSACTION}/${transaction.uid}`); - const data = (await this.transactionService.get(refSource))!; - if (transaction.payload.nft) { - if (transaction.payload.type === TransactionPayloadType.NFT_PURCHASE) { - const payload = data.payload; - payload.void = true; - this.transactionService.push({ ref: refSource, data: data, action: 'update' }); + protected async setNftOwner(order: Transaction, payment: Transaction): Promise { + const nftDocRef = build5Db().collection(COL.NFT).doc(payment.payload.nft!); + const nft = await this.transactionService.get(nftDocRef); + + const nftUpdateData = { + owner: payment.member, + isOwned: true, + price: nft.saleAccess === NftAccess.MEMBERS ? nft.price : payment.payload.amount, + sold: true, + locked: false, + lockedBy: null, + hidden: false, + soldOn: nft.soldOn || serverTime(), + availableFrom: null, + availablePrice: null, + auctionFrom: null, + auctionTo: null, + extendedAuctionTo: null, + auctionFloorPrice: null, + auctionLength: null, + extendedAuctionLength: null, + auctionHighestBid: null, + auctionHighestBidder: null, + auctionHighestTransaction: null, + saleAccess: null, + saleAccessMembers: [], + }; + this.transactionService.push({ + ref: nftDocRef, + data: nftUpdateData, + action: 'update', + }); + + if ( + nft.auctionHighestTransaction && + order.payload.type === TransactionPayloadType.NFT_PURCHASE + ) { + const highestTranDocRef = build5Db().doc( + `${COL.TRANSACTION}/${nft.auctionHighestTransaction}`, + ); + const highestPay = (await highestTranDocRef.get())!; + this.transactionService.push({ + ref: highestTranDocRef, + data: { invalidPayment: true }, + action: 'update', + }); - // Unlock NFT. - const refNft = build5Db().collection(COL.NFT).doc(transaction.payload.nft); + const sameOwner = highestPay.member === order.member; + const credit = await this.transactionService.createCredit( + TransactionPayloadType.NONE, + highestPay, + { + msgId: highestPay.payload.chainReference || '', + to: { + address: highestPay.payload.targetAddress!, + amount: highestPay.payload.amount!, + }, + from: { + address: highestPay.payload.sourceAddress!, + amount: highestPay.payload.amount!, + }, + }, + serverTime(), + sameOwner, + ); + + if (!sameOwner) { + const orderId = Array.isArray(highestPay.payload.sourceTransaction) + ? last(highestPay.payload.sourceTransaction)! + : highestPay.payload.sourceTransaction!; + const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${orderId}`); this.transactionService.push({ - ref: refNft, - data: { locked: false, lockedBy: null }, + ref: orderDocRef, + data: { linkedTransactions: build5Db().arrayUnion(credit?.uid) }, + action: 'update', + }); + } + } + + if (order.payload.beneficiary === 'space') { + const collectionDocRef = build5Db().doc(`${COL.COLLECTION}/${payment.payload.collection}`); + this.transactionService.push({ + ref: collectionDocRef, + data: { sold: build5Db().inc(1) }, + action: 'update', + }); + + const collection = (await this.transactionService.get(collectionDocRef))!; + if (collection.placeholderNft && collection.total === collection.sold + 1) { + const placeholderNftDocRef = build5Db().doc(`${COL.NFT}/${collection.placeholderNft}`); + this.transactionService.push({ + ref: placeholderNftDocRef, + data: { + sold: true, + owner: null, + availablePrice: null, + availableFrom: null, + soldOn: serverTime(), + hidden: false, + }, action: 'update', }); - } else if (transaction.payload.type === TransactionPayloadType.NFT_BID) { - const payments = await build5Db() - .collection(COL.TRANSACTION) - .where('payload.invalidPayment', '==', false) - .where('payload.sourceTransaction', 'array-contains', transaction.uid) - .orderBy('payload.amount', 'desc') - .get(); - if (payments.length === 0) { - const payload = data.payload; - payload.void = true; - this.transactionService.push({ ref: refSource, data: data, action: 'update' }); - } } - } else { - const payload = data.payload; - payload.void = true; - this.transactionService.push({ ref: refSource, data, action: 'update' }); } } - private async addNewBid(transaction: Transaction, payment: Transaction): Promise { + protected async addNewBid(transaction: Transaction, payment: Transaction): Promise { const nftDocRef = build5Db().collection(COL.NFT).doc(transaction.payload.nft!); const paymentDocRef = build5Db().doc(`${COL.TRANSACTION}/${payment.uid}`); const nft = await this.transactionService.get(nftDocRef); @@ -337,119 +295,4 @@ export class NftService { }); } } - - private async setNftOwner(order: Transaction, payment: Transaction): Promise { - const nftDocRef = build5Db().collection(COL.NFT).doc(payment.payload.nft!); - const nft = await this.transactionService.get(nftDocRef); - - const nftUpdateData = { - owner: payment.member, - isOwned: true, - price: nft.saleAccess === NftAccess.MEMBERS ? nft.price : payment.payload.amount, - sold: true, - locked: false, - lockedBy: null, - hidden: false, - soldOn: nft.soldOn || serverTime(), - availableFrom: null, - availablePrice: null, - auctionFrom: null, - auctionTo: null, - extendedAuctionTo: null, - auctionFloorPrice: null, - auctionLength: null, - extendedAuctionLength: null, - auctionHighestBid: null, - auctionHighestBidder: null, - auctionHighestTransaction: null, - saleAccess: null, - saleAccessMembers: [], - }; - this.transactionService.push({ - ref: nftDocRef, - data: nftUpdateData, - action: 'update', - }); - - if ( - nft.auctionHighestTransaction && - order.payload.type === TransactionPayloadType.NFT_PURCHASE - ) { - const highestTranDocRef = build5Db().doc( - `${COL.TRANSACTION}/${nft.auctionHighestTransaction}`, - ); - const highestPay = (await highestTranDocRef.get())!; - this.transactionService.push({ - ref: highestTranDocRef, - data: { invalidPayment: true }, - action: 'update', - }); - - const sameOwner = highestPay.member === order.member; - const credit = await this.transactionService.createCredit( - TransactionPayloadType.NONE, - highestPay, - { - msgId: highestPay.payload.chainReference || '', - to: { - address: highestPay.payload.targetAddress!, - amount: highestPay.payload.amount!, - }, - from: { - address: highestPay.payload.sourceAddress!, - amount: highestPay.payload.amount!, - }, - }, - serverTime(), - sameOwner, - ); - - if (!sameOwner) { - const orderId = Array.isArray(highestPay.payload.sourceTransaction) - ? last(highestPay.payload.sourceTransaction)! - : highestPay.payload.sourceTransaction!; - const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${orderId}`); - this.transactionService.push({ - ref: orderDocRef, - data: { linkedTransactions: build5Db().arrayUnion(credit?.uid) }, - action: 'update', - }); - } - } - - if (order.payload.beneficiary === 'space') { - const collectionDocRef = build5Db().doc(`${COL.COLLECTION}/${payment.payload.collection}`); - this.transactionService.push({ - ref: collectionDocRef, - data: { sold: build5Db().inc(1) }, - action: 'update', - }); - - const collection = (await this.transactionService.get(collectionDocRef))!; - if (collection.placeholderNft && collection.total === collection.sold + 1) { - const placeholderNftDocRef = build5Db().doc(`${COL.NFT}/${collection.placeholderNft}`); - this.transactionService.push({ - ref: placeholderNftDocRef, - data: { - sold: true, - owner: null, - availablePrice: null, - availableFrom: null, - soldOn: serverTime(), - hidden: false, - }, - action: 'update', - }); - } - } - } - - private setTradingStats = (nft: Nft) => { - const data = { lastTradedOn: serverTime(), totalTrades: build5Db().inc(1) }; - const collectionDocRef = build5Db().doc(`${COL.COLLECTION}/${nft.collection}`); - this.transactionService.push({ ref: collectionDocRef, data, action: 'update' }); - - const nftDocRef = build5Db().doc(`${COL.NFT}/${nft.uid}`); - this.transactionService.push({ ref: nftDocRef, data, action: 'update' }); - }; } diff --git a/packages/functions/src/services/payment/nft/nft-bid.service.ts b/packages/functions/src/services/payment/nft/nft-bid.service.ts new file mode 100644 index 0000000000..ef37898c48 --- /dev/null +++ b/packages/functions/src/services/payment/nft/nft-bid.service.ts @@ -0,0 +1,90 @@ +import { build5Db } from '@build-5/database'; +import { COL, Member, Nft, Transaction } from '@build-5/interfaces'; +import { last } from 'lodash'; +import { AVAILABLE_NETWORKS } from '../../../controls/common'; +import { NotificationService } from '../../notification/notification'; +import { HandlerParams } from '../base'; +import { BaseNftService } from './common'; + +export class NftBidService extends BaseNftService { + public handleRequest = async ({ order, match, tran, tranEntry, build5Tran }: HandlerParams) => { + const nftDocRef = build5Db().collection(COL.NFT).doc(order.payload.nft!); + const nft = await this.transactionService.get(nftDocRef); + if (nft?.auctionFrom) { + const payment = await this.transactionService.createPayment(order, match); + await this.addNewBid(order, payment); + } else { + await this.transactionService.processAsInvalid(tran, order, tranEntry, build5Tran); + } + }; + + public async markNftAsFinalized({ uid, auctionFrom }: Nft): Promise { + if (!auctionFrom) { + throw new Error('NFT auctionFrom is no longer defined'); + } + + const nftDocRef = build5Db().doc(`${COL.NFT}/${uid}`); + const nft = await this.transactionService.get(nftDocRef); + if (nft.auctionHighestTransaction) { + const highestPayDocRef = build5Db().doc( + `${COL.TRANSACTION}/${nft.auctionHighestTransaction}`, + ); + const highestPay = (await highestPayDocRef.get())!; + + const orderId = Array.isArray(highestPay.payload.sourceTransaction) + ? last(highestPay.payload.sourceTransaction)! + : highestPay.payload.sourceTransaction!; + const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${orderId}`); + const order = await orderDocRef.get(); + if (!order) { + throw new Error('Unable to find ORDER linked to PAYMENT'); + } + + this.transactionService.markAsReconciled(order, highestPay.payload.chainReference!); + this.transactionService.createBillPayment(order, highestPay); + await this.setNftOwner(order, highestPay); + + const memberDocRef = build5Db().collection(COL.MEMBER).doc(order.member!); + const member = await memberDocRef.get(); + + const notification = NotificationService.prepareWinBid(member, nft, highestPay); + const notificationDocRef = build5Db().doc(`${COL.NOTIFICATION}/${notification.uid}`); + this.transactionService.push({ + ref: notificationDocRef, + data: notification, + action: 'set', + }); + this.transactionService.push({ + ref: orderDocRef, + data: { + linkedTransactions: build5Db().arrayUnion(...this.transactionService.linkedTransactions), + }, + action: 'update', + }); + + this.setTradingStats(nft); + + const tanglePuchase = order.payload.tanglePuchase; + const disableWithdraw = order.payload.disableWithdraw; + if (!disableWithdraw && tanglePuchase && AVAILABLE_NETWORKS.includes(order.network!)) { + await this.withdrawNft(order, nft); + } + } else { + this.transactionService.push({ + ref: nftDocRef, + data: { + auctionFrom: null, + auctionTo: null, + extendedAuctionTo: null, + auctionFloorPrice: null, + auctionLength: null, + extendedAuctionLength: null, + auctionHighestBid: null, + auctionHighestBidder: null, + auctionHighestTransaction: null, + }, + action: 'update', + }); + } + } +} diff --git a/packages/functions/src/services/payment/nft/nft-deposit-service.ts b/packages/functions/src/services/payment/nft/nft-deposit.service.ts similarity index 96% rename from packages/functions/src/services/payment/nft/nft-deposit-service.ts rename to packages/functions/src/services/payment/nft/nft-deposit.service.ts index d63be5a5d0..9ae8de9fe6 100644 --- a/packages/functions/src/services/payment/nft/nft-deposit-service.ts +++ b/packages/functions/src/services/payment/nft/nft-deposit.service.ts @@ -38,17 +38,13 @@ import { getRandomEthAddress } from '../../../utils/wallet.utils'; import { NftWallet } from '../../wallet/NftWallet'; import { SmrWallet } from '../../wallet/SmrWalletService'; import { WalletService } from '../../wallet/wallet'; -import { TransactionMatch, TransactionService } from '../transaction-service'; -export class NftDepositService { - constructor(readonly transactionService: TransactionService) {} +import { BaseService, HandlerParams } from '../base'; +import { TransactionMatch } from '../transaction-service'; - public handleNftDepositRequest = async ( - order: Transaction, - milestoneTransaction: MilestoneTransactionEntry, - match: TransactionMatch, - ) => { +export class NftDepositService extends BaseService { + public handleRequest = async ({ order, match, tranEntry }: HandlerParams) => { try { - const nft = await this.depositNft(order, milestoneTransaction, match); + const nft = await this.depositNft(order, tranEntry, match); await this.transactionService.createPayment(order, match); this.transactionService.markAsReconciled(order, match.msgId); diff --git a/packages/functions/src/services/payment/nft/nft-purchase.service.ts b/packages/functions/src/services/payment/nft/nft-purchase.service.ts new file mode 100644 index 0000000000..e3e9c93285 --- /dev/null +++ b/packages/functions/src/services/payment/nft/nft-purchase.service.ts @@ -0,0 +1,66 @@ +import { build5Db } from '@build-5/database'; +import { COL, Nft, Transaction, TransactionPayloadType } from '@build-5/interfaces'; +import { AVAILABLE_NETWORKS } from '../../../controls/common'; +import { HandlerParams } from '../base'; +import { BaseNftService } from './common'; + +export class NftPurchaseService extends BaseNftService { + public handleRequest = async ({ order, match, tran, tranEntry, build5Tran }: HandlerParams) => { + const nftDocRef = build5Db().doc(`${COL.NFT}/${order.payload.nft}`); + const nft = await this.transactionService.get(nftDocRef); + + if (nft.availableFrom === null) { + await this.transactionService.processAsInvalid(tran, order, tranEntry, build5Tran); + return; + } + + const payment = await this.transactionService.createPayment(order, match); + this.transactionService.createBillPayment(order, payment); + await this.setNftOwner(order, payment); + this.transactionService.markAsReconciled(order, match.msgId); + + this.setTradingStats(nft); + + const tanglePuchase = order.payload.tanglePuchase; + const disableWithdraw = order.payload.disableWithdraw; + if (!disableWithdraw && tanglePuchase && AVAILABLE_NETWORKS.includes(order.network!)) { + await this.withdrawNft(order, nft); + } + }; + + public markAsVoid = async (transaction: Transaction): Promise => { + const refSource = build5Db().doc(`${COL.TRANSACTION}/${transaction.uid}`); + const data = (await this.transactionService.get(refSource))!; + if (transaction.payload.nft) { + if (transaction.payload.type === TransactionPayloadType.NFT_PURCHASE) { + const payload = data.payload; + payload.void = true; + this.transactionService.push({ ref: refSource, data: data, action: 'update' }); + + // Unlock NFT. + const refNft = build5Db().collection(COL.NFT).doc(transaction.payload.nft); + this.transactionService.push({ + ref: refNft, + data: { locked: false, lockedBy: null }, + action: 'update', + }); + } else if (transaction.payload.type === TransactionPayloadType.NFT_BID) { + const payments = await build5Db() + .collection(COL.TRANSACTION) + .where('payload.invalidPayment', '==', false) + .where('payload.sourceTransaction', 'array-contains', transaction.uid) + .orderBy('payload.amount', 'desc') + .get(); + if (payments.length === 0) { + const payload = data.payload; + payload.void = true; + this.transactionService.push({ ref: refSource, data: data, action: 'update' }); + } + } + } else { + const payload = data.payload; + payload.void = true; + this.transactionService.push({ ref: refSource, data, action: 'update' }); + } + }; +} diff --git a/packages/functions/src/services/payment/nft/nft-stake-service.ts b/packages/functions/src/services/payment/nft/nft-stake.service.ts similarity index 92% rename from packages/functions/src/services/payment/nft/nft-stake-service.ts rename to packages/functions/src/services/payment/nft/nft-stake.service.ts index 6b1c37755d..f90c0e3f60 100644 --- a/packages/functions/src/services/payment/nft/nft-stake-service.ts +++ b/packages/functions/src/services/payment/nft/nft-stake.service.ts @@ -24,17 +24,12 @@ import { dateToTimestamp } from '../../../utils/dateTime.utils'; import { getRandomEthAddress } from '../../../utils/wallet.utils'; import { SmrWallet } from '../../wallet/SmrWalletService'; import { WalletService } from '../../wallet/wallet'; +import { BaseService, HandlerParams } from '../base'; import { createNftWithdrawOrder } from '../tangle-service/nft/nft-purchase.service'; -import { TransactionMatch, TransactionService } from '../transaction-service'; -import { NftDepositService } from './nft-deposit-service'; -export class NftStakeService { - constructor(readonly transactionService: TransactionService) {} +import { NftDepositService } from './nft-deposit.service'; - public handleNftStake = async ( - order: Transaction, - match: TransactionMatch, - tranEntry: MilestoneTransactionEntry, - ) => { +export class NftStakeService extends BaseService { + public handleRequest = async ({ order, match, tranEntry }: HandlerParams) => { let customErrorParams = {}; try { if (!tranEntry.nftOutput) { diff --git a/packages/functions/src/services/payment/payment-processing.ts b/packages/functions/src/services/payment/payment-processing.ts index 797739c3d4..e5bfde59c5 100644 --- a/packages/functions/src/services/payment/payment-processing.ts +++ b/packages/functions/src/services/payment/payment-processing.ts @@ -1,254 +1,209 @@ -import { ITransaction, build5Db } from '@build-5/database'; +import { build5Db } from '@build-5/database'; import { COL, - Entity, MilestoneTransaction, MilestoneTransactionEntry, NetworkAddress, - Nft, Transaction, TransactionPayloadType, TransactionType, + WenError, } from '@build-5/interfaces'; import dayjs from 'dayjs'; -import { isEmpty } from 'lodash'; +import { head, isEmpty } from 'lodash'; import { dateToTimestamp } from '../../utils/dateTime.utils'; -import { AddressService } from './address-service'; +import { invalidArgument } from '../../utils/error.utils'; +import { MemberAddressService } from './address/address-member.service'; +import { SpaceAddressService } from './address/address.space.service'; import { AwardService } from './award/award-service'; +import { HandlerParams } from './base'; import { CreditService } from './credit-service'; import { MetadataNftService } from './metadataNft-service'; -import { CollectionMintingService } from './nft/collection-minting-service'; -import { NftDepositService } from './nft/nft-deposit-service'; -import { NftService } from './nft/nft-service'; -import { NftStakeService } from './nft/nft-stake-service'; +import { CollectionMintingService } from './nft/collection-minting.service'; +import { NftBidService } from './nft/nft-bid.service'; +import { NftDepositService } from './nft/nft-deposit.service'; +import { NftPurchaseService } from './nft/nft-purchase.service'; +import { NftStakeService } from './nft/nft-stake.service'; import { SpaceService } from './space/space-service'; import { StakeService } from './stake-service'; import { TangleRequestService } from './tangle-service/TangleRequestService'; import { ImportMintedTokenService } from './token/import-minted-token.service'; -import { MintedTokenClaimService } from './token/minted-token-claim'; -import { TokenMintService } from './token/token-mint-service'; -import { TokenService } from './token/token-service'; +import { TokenAirdropClaimService } from './token/token-airdrop-claim.service'; +import { TokenMintService } from './token/token-mint.service'; +import { TokenMintedAirdropService } from './token/token-minted-airdrop.service'; +import { MintedTokenClaimService } from './token/token-minted-claim.service'; +import { TokenPurchaseService } from './token/token-purchase.service'; +import { TokenTradeService } from './token/token-trade.service'; import { TransactionService } from './transaction-service'; import { VotingService } from './voting-service'; export class ProcessingService { - private transactionService: TransactionService; - private tokenService: TokenService; - private tokenMintService: TokenMintService; - private mintedTokenClaimService: MintedTokenClaimService; - private nftService: NftService; - private addressService: AddressService; - private collectionMintingService: CollectionMintingService; - private creditService: CreditService; - private stakeService: StakeService; - private tangleRequestService: TangleRequestService; - private votingService: VotingService; + public markAsVoid = (nftService: NftPurchaseService, transaction: Transaction) => + nftService.markAsVoid(transaction); - constructor(transaction: ITransaction) { - this.transactionService = new TransactionService(transaction); - this.tokenService = new TokenService(this.transactionService); - this.tokenMintService = new TokenMintService(this.transactionService); - this.mintedTokenClaimService = new MintedTokenClaimService(this.transactionService); - this.nftService = new NftService(this.transactionService); - this.addressService = new AddressService(this.transactionService); - this.collectionMintingService = new CollectionMintingService(this.transactionService); - this.creditService = new CreditService(this.transactionService); - this.stakeService = new StakeService(this.transactionService); - this.tangleRequestService = new TangleRequestService(this.transactionService); - this.votingService = new VotingService(this.transactionService); - } - - public submit = () => this.transactionService.submit(); - - public markAsVoid = (transaction: Transaction) => this.nftService.markAsVoid(transaction); + public processMilestoneTransactions = async (tran: MilestoneTransaction): Promise => { + const build5Transaction = await this.getBuild5Transaction(tran); - public markNftAsFinalized = (nft: Nft) => this.nftService.markNftAsFinalized(nft); - - public async processMilestoneTransactions(tran: MilestoneTransaction): Promise { - if (!tran.outputs?.length) { - return; - } - const build5Transaction = isEmpty(tran.build5TransactionId) - ? undefined - : await build5Db().doc(`${COL.TRANSACTION}/${tran.build5TransactionId}`).get(); for (const tranOutput of tran.outputs) { - if ( - build5Transaction?.type !== TransactionType.UNLOCK && - tran.inputs.find((i) => tranOutput.address === i.address) - ) { + const isSourceAddress = tran.inputs.find((i) => i.address === tranOutput.address); + if (build5Transaction?.type !== TransactionType.UNLOCK && isSourceAddress) { continue; } - const orders = await this.findAllOrdersWithAddress(tranOutput.address); - for (const order of orders) { - await this.processOrderTransaction(tran, tranOutput, order.uid, build5Transaction); + + const order = await this.findOrderForAddress(tranOutput.address); + if (order) { + await build5Db().runTransaction(async (transaction) => { + const tranService = new TransactionService(transaction); + await this.processOrderTransaction( + tranService, + tran, + tranOutput, + order.uid, + build5Transaction, + ); + tranService.submit(); + }); } } - } + }; private async processOrderTransaction( + tranService: TransactionService, tran: MilestoneTransaction, - tranOutput: MilestoneTransactionEntry, + tranEntry: MilestoneTransactionEntry, orderId: string, - build5Transaction: Transaction | undefined, + build5Tran: Transaction | undefined, ): Promise { const orderRef = build5Db().doc(`${COL.TRANSACTION}/${orderId}`); - const order = await this.transactionService.get(orderRef); + const order = await tranService.transaction.get(orderRef); if (!order) { return; } - // This happens here on purpose instead of cron to reduce $$$ const expireDate = dayjs(order.payload.expiresOn?.toDate()); let expired = false; if (expireDate.isBefore(dayjs(), 'ms')) { - await this.markAsVoid(order); + const nftService = new NftPurchaseService(tranService); + await this.markAsVoid(nftService, order); expired = true; } - // Let's process this.' - const match = await this.transactionService.isMatch(tran, tranOutput, order, build5Transaction); - if (!expired && order.payload.reconciled === false && order.payload.void === false && match) { - const expirationUnlock = this.transactionService.getExpirationUnlock( - tranOutput.unlockConditions, - ); + const match = await tranService.isMatch(tran, tranEntry, order, build5Tran); + if (!expired && !order.payload.reconciled && !order.payload.void && match) { + const expirationUnlock = tranService.getExpirationUnlock(tranEntry.unlockConditions); if (expirationUnlock !== undefined) { - const type = tranOutput.nftOutput + const type = tranEntry.nftOutput ? TransactionPayloadType.UNLOCK_NFT : TransactionPayloadType.UNLOCK_FUNDS; - await this.transactionService.createUnlockTransaction( + await tranService.createUnlockTransaction( order, tran, - tranOutput, + tranEntry, type, - tranOutput.outputId, + tranEntry.outputId, dateToTimestamp(dayjs.unix(expirationUnlock.unixTime)), ); return; } - switch (order.payload.type) { - case TransactionPayloadType.NFT_PURCHASE: - await this.nftService.handleNftPurchaseRequest( - tran, - tranOutput, - order, - match, - build5Transaction, - ); - break; - case TransactionPayloadType.NFT_BID: - await this.nftService.handleNftBidRequest( - tran, - tranOutput, - order, - match, - build5Transaction, - ); - break; - case TransactionPayloadType.SPACE_ADDRESS_VALIDATION: - await this.addressService.handleSpaceAddressValidationRequest(order, match); - break; - case TransactionPayloadType.MEMBER_ADDRESS_VALIDATION: - await this.addressService.handleAddressValidationRequest(order, match, Entity.MEMBER); - break; - case TransactionPayloadType.TOKEN_PURCHASE: - await this.tokenService.handleTokenPurchaseRequest(order, match); - break; - case TransactionPayloadType.TOKEN_AIRDROP: - await this.tokenService.handleTokenAirdropClaim(order, match); - break; - case TransactionPayloadType.AIRDROP_MINTED_TOKEN: - await this.tokenService.handleMintedTokenAirdrop(order, tranOutput, match); - break; - case TransactionPayloadType.MINT_TOKEN: - await this.tokenMintService.handleMintingRequest(order, match); - break; - case TransactionPayloadType.CLAIM_MINTED_TOKEN: - await this.mintedTokenClaimService.handleClaimRequest(order, match); - break; - case TransactionPayloadType.SELL_TOKEN: - case TransactionPayloadType.BUY_TOKEN: - await this.tokenService.handleTokenTradeRequest( - order, - tranOutput, - match, - build5Transaction, - ); - break; - case TransactionPayloadType.MINT_COLLECTION: - await this.collectionMintingService.handleCollectionMintingRequest(order, match); - break; - case TransactionPayloadType.DEPOSIT_NFT: { - const service = new NftDepositService(this.transactionService); - await service.handleNftDepositRequest(order, tranOutput, match); - break; - } - case TransactionPayloadType.CREDIT_LOCKED_FUNDS: - await this.creditService.handleCreditUnrefundableOrder(order, match); - break; - case TransactionPayloadType.STAKE: - await this.stakeService.handleStakeOrder(order, match); - break; - case TransactionPayloadType.TANGLE_REQUEST: - await this.tangleRequestService.onTangleRequest( - order, - tran, - tranOutput, - match, - build5Transaction, - ); - break; - case TransactionPayloadType.PROPOSAL_VOTE: - await this.votingService.handleTokenVoteRequest(order, match); - break; - case TransactionPayloadType.CLAIM_SPACE: { - const service = new SpaceService(this.transactionService); - await service.handleSpaceClaim(order, match); - break; - } - case TransactionPayloadType.STAKE_NFT: { - const service = new NftStakeService(this.transactionService); - await service.handleNftStake(order, match, tranOutput); - break; - } - case TransactionPayloadType.FUND_AWARD: { - const service = new AwardService(this.transactionService); - await service.handleAwardFundingOrder(order, match); - break; - } - case TransactionPayloadType.IMPORT_TOKEN: { - const service = new ImportMintedTokenService(this.transactionService); - await service.handleMintedTokenImport(order, match); - break; - } - case TransactionPayloadType.MINT_METADATA_NFT: { - const service = new MetadataNftService(this.transactionService); - await service.handleMintMetadataNftRequest(order, match); - break; - } - } + const serviceParams: HandlerParams = { + network: order.network, + payment: undefined, + match, + order, + owner: order.member || '', + tran, + tranEntry, + orderId: order.uid, + build5Tran, + tangleOrder: {} as Transaction, + request: {}, + }; + const service = this.getService(tranService, order.payload.type!); + await service.handleRequest(serviceParams); } else { - await this.transactionService.processAsInvalid(tran, order, tranOutput, build5Transaction); + await tranService.processAsInvalid(tran, order, tranEntry, build5Tran); } // Add linked transaction. - this.transactionService.push({ + tranService.push({ ref: orderRef, data: { linkedTransactions: [ ...(order.linkedTransactions || []), - ...this.transactionService.linkedTransactions, + ...tranService.linkedTransactions, ], }, action: 'update', }); } - private findAllOrdersWithAddress = (address: NetworkAddress) => - build5Db() + private getService = (tranService: TransactionService, type: TransactionPayloadType) => { + switch (type) { + case TransactionPayloadType.NFT_PURCHASE: + return new NftPurchaseService(tranService); + case TransactionPayloadType.NFT_BID: + return new NftBidService(tranService); + case TransactionPayloadType.SPACE_ADDRESS_VALIDATION: + return new SpaceAddressService(tranService); + case TransactionPayloadType.MEMBER_ADDRESS_VALIDATION: + return new MemberAddressService(tranService); + case TransactionPayloadType.TOKEN_PURCHASE: + return new TokenPurchaseService(tranService); + case TransactionPayloadType.TOKEN_AIRDROP: + return new TokenAirdropClaimService(tranService); + case TransactionPayloadType.AIRDROP_MINTED_TOKEN: + new TokenMintedAirdropService(tranService); + case TransactionPayloadType.MINT_TOKEN: + return new TokenMintService(tranService); + case TransactionPayloadType.CLAIM_MINTED_TOKEN: + return new MintedTokenClaimService(tranService); + case TransactionPayloadType.SELL_TOKEN: + case TransactionPayloadType.BUY_TOKEN: + return new TokenTradeService(tranService); + case TransactionPayloadType.MINT_COLLECTION: + return new CollectionMintingService(tranService); + case TransactionPayloadType.DEPOSIT_NFT: + return new NftDepositService(tranService); + case TransactionPayloadType.CREDIT_LOCKED_FUNDS: + return new CreditService(tranService); + case TransactionPayloadType.STAKE: + return new StakeService(tranService); + case TransactionPayloadType.TANGLE_REQUEST: + return new TangleRequestService(tranService); + case TransactionPayloadType.PROPOSAL_VOTE: + return new VotingService(tranService); + case TransactionPayloadType.CLAIM_SPACE: + return new SpaceService(tranService); + case TransactionPayloadType.STAKE_NFT: + return new NftStakeService(tranService); + case TransactionPayloadType.FUND_AWARD: + return new AwardService(tranService); + case TransactionPayloadType.IMPORT_TOKEN: + return new ImportMintedTokenService(tranService); + case TransactionPayloadType.MINT_METADATA_NFT: + return new MetadataNftService(tranService); + default: + throw invalidArgument(WenError.invalid_tangle_request_type); + } + }; + + private findOrderForAddress = async (address: NetworkAddress) => { + const snap = await build5Db() .collection(COL.TRANSACTION) .where('type', '==', TransactionType.ORDER) .where('payload.targetAddress', '==', address) + .limit(1) .get(); + return head(snap); + }; + + private getBuild5Transaction = (tran: MilestoneTransaction) => { + if (isEmpty(tran.build5TransactionId)) { + return; + } + const docRef = build5Db().doc(`${COL.TRANSACTION}/${tran.build5TransactionId}`); + return docRef.get(); + }; } diff --git a/packages/functions/src/services/payment/space/space-service.ts b/packages/functions/src/services/payment/space/space-service.ts index 6f05c643a7..636dfe64aa 100644 --- a/packages/functions/src/services/payment/space/space-service.ts +++ b/packages/functions/src/services/payment/space/space-service.ts @@ -6,7 +6,6 @@ import { SUB_COL, Space, SpaceMember, - Transaction, TransactionPayloadType, } from '@build-5/interfaces'; import { @@ -22,12 +21,10 @@ import { Bech32AddressHelper } from '../../../utils/bech32-address.helper'; import { serverTime } from '../../../utils/dateTime.utils'; import { SmrWallet } from '../../wallet/SmrWalletService'; import { WalletService } from '../../wallet/wallet'; -import { TransactionMatch, TransactionService } from '../transaction-service'; +import { BaseService, HandlerParams } from '../base'; -export class SpaceService { - constructor(readonly transactionService: TransactionService) {} - - public handleSpaceClaim = async (order: Transaction, match: TransactionMatch) => { +export class SpaceService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { const payment = await this.transactionService.createPayment(order, match); await this.transactionService.createCredit( TransactionPayloadType.SPACE_CALIMED, diff --git a/packages/functions/src/services/payment/stake-service.ts b/packages/functions/src/services/payment/stake-service.ts index 7b8f38f2b0..37ed7c4f30 100644 --- a/packages/functions/src/services/payment/stake-service.ts +++ b/packages/functions/src/services/payment/stake-service.ts @@ -1,4 +1,3 @@ -import { build5Db } from '@build-5/database'; import { COL, Entity, @@ -14,12 +13,11 @@ import dayjs from 'dayjs'; import { get } from 'lodash'; import { dateToTimestamp } from '../../utils/dateTime.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; -import { TransactionMatch, TransactionService } from './transaction-service'; - -export class StakeService { - constructor(readonly transactionService: TransactionService) {} +import { BaseService, HandlerParams } from './base'; +import { build5Db } from '@build-5/database'; - public handleStakeOrder = async (order: Transaction, match: TransactionMatch) => { +export class StakeService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { const payment = await this.transactionService.createPayment(order, match); const matchAmount = match.to.amount; diff --git a/packages/functions/src/services/payment/tangle-service/TangleRequestService.ts b/packages/functions/src/services/payment/tangle-service/TangleRequestService.ts index ed21af54b1..0363b44cac 100644 --- a/packages/functions/src/services/payment/tangle-service/TangleRequestService.ts +++ b/packages/functions/src/services/payment/tangle-service/TangleRequestService.ts @@ -2,8 +2,6 @@ import { build5Db } from '@build-5/database'; import { COL, Member, - MilestoneTransaction, - MilestoneTransactionEntry, Network, NetworkAddress, TangleRequestType, @@ -15,7 +13,7 @@ import { get } from 'lodash'; import { getOutputMetadata } from '../../../utils/basic-output.utils'; import { invalidArgument } from '../../../utils/error.utils'; import { getRandomNonce } from '../../../utils/wallet.utils'; -import { TransactionMatch, TransactionService } from '../transaction-service'; +import { BaseService, HandlerParams } from '../base'; import { TangleAddressValidationService } from './address/address-validation.service'; import { AwardApproveParticipantService } from './award/award.approve.participant.service'; import { AwardCreateService } from './award/award.create.service'; @@ -38,16 +36,10 @@ import { SpaceLeaveService } from './space/SpaceLeaveService'; import { TangleStakeService } from './token/stake.service'; import { TangleTokenClaimService } from './token/token-claim.service'; import { TangleTokenTradeService } from './token/token-trade.service'; -export class TangleRequestService { - constructor(readonly transactionService: TransactionService) {} - public onTangleRequest = async ( - order: Transaction, - tran: MilestoneTransaction, - tranEntry: MilestoneTransactionEntry, - match: TransactionMatch, - build5Transaction?: Transaction, - ) => { +export class TangleRequestService extends BaseService { + public handleRequest = async (params: HandlerParams) => { + const { match, order, tranEntry } = params; let owner = match.from.address; let payment: Transaction | undefined; @@ -55,16 +47,11 @@ export class TangleRequestService { owner = await this.getOwner(match.from.address, order.network!); payment = await this.transactionService.createPayment({ ...order, member: owner }, match); const request = getOutputMetadata(tranEntry.output).request; - const response = await this.handleTangleRequest( - order, - match, - payment, - tran, - tranEntry, - owner, - request, - build5Transaction, - ); + + const serviceParams = { ...params, request, payment, owner }; + const service = this.getService(serviceParams); + const response = await service.handleRequest(serviceParams); + if (response) { this.transactionService.createTangleCredit( payment, @@ -91,124 +78,59 @@ export class TangleRequestService { } }; - public handleTangleRequest = async ( - order: Transaction, - match: TransactionMatch, - payment: Transaction, - tran: MilestoneTransaction, - tranEntry: MilestoneTransactionEntry, - owner: string, - request: Record, - build5Transaction?: Transaction, - ) => { - if (tranEntry.nftOutput) { - const service = new NftDepositService(this.transactionService); - return await service.handleNftDeposit(order.network!, owner, tran, tranEntry); + private getService = (params: HandlerParams) => { + if (params.tranEntry.nftOutput) { + return new NftDepositService(this.transactionService); } - switch (request.requestType) { - case TangleRequestType.ADDRESS_VALIDATION: { - const service = new TangleAddressValidationService(this.transactionService); - return await service.handeAddressValidation(order, tran, tranEntry, owner, request); - } + switch (params.request.requestType) { + case TangleRequestType.ADDRESS_VALIDATION: + return new TangleAddressValidationService(this.transactionService); case TangleRequestType.BUY_TOKEN: - case TangleRequestType.SELL_TOKEN: { - const service = new TangleTokenTradeService(this.transactionService); - return await service.handleTokenTradeTangleRequest( - match, - payment, - tran, - tranEntry, - owner, - request, - build5Transaction, - ); - } - case TangleRequestType.STAKE: { - const service = new TangleStakeService(this.transactionService); - return await service.handleStaking(tran, tranEntry, owner, request); - } - case TangleRequestType.NFT_PURCHASE: { - const service = new TangleNftPurchaseService(this.transactionService); - return await service.handleNftPurchase(tran, tranEntry, owner, request); - } - case TangleRequestType.NFT_SET_FOR_SALE: { - const service = new TangleNftSetForSaleService(this.transactionService); - return await service.handleNftSetForSale(owner, request); - } - case TangleRequestType.NFT_BID: { - const service = new TangleNftBidService(this.transactionService); - return await service.handleNftBid(tran, tranEntry, owner, request); - } - case TangleRequestType.CLAIM_MINTED_AIRDROPS: { - const service = new TangleTokenClaimService(this.transactionService); - return await service.handleMintedTokenAirdropRequest(owner, request); - } - case TangleRequestType.AWARD_CREATE: { - const service = new AwardCreateService(this.transactionService); - return await service.handleCreateRequest(owner, request); - } - case TangleRequestType.AWARD_FUND: { - const service = new AwardFundService(this.transactionService); - return await service.handleFundRequest(owner, request); - } - case TangleRequestType.AWARD_APPROVE_PARTICIPANT: { - const service = new AwardApproveParticipantService(this.transactionService); - return await service.handleApproveParticipantRequest(owner, request); - } - case TangleRequestType.PROPOSAL_CREATE: { - const service = new ProposalCreateService(this.transactionService); - return await service.handleProposalCreateRequest(owner, request); - } + return new TangleTokenTradeService(this.transactionService); + case TangleRequestType.SELL_TOKEN: + return new TangleTokenTradeService(this.transactionService); + case TangleRequestType.STAKE: + return new TangleStakeService(this.transactionService); + case TangleRequestType.NFT_PURCHASE: + return new TangleNftPurchaseService(this.transactionService); + case TangleRequestType.NFT_SET_FOR_SALE: + return new TangleNftSetForSaleService(this.transactionService); + case TangleRequestType.NFT_BID: + return new TangleNftBidService(this.transactionService); + case TangleRequestType.CLAIM_MINTED_AIRDROPS: + return new TangleTokenClaimService(this.transactionService); + case TangleRequestType.AWARD_CREATE: + return new AwardCreateService(this.transactionService); + case TangleRequestType.AWARD_FUND: + return new AwardFundService(this.transactionService); + case TangleRequestType.AWARD_APPROVE_PARTICIPANT: + return new AwardApproveParticipantService(this.transactionService); + case TangleRequestType.PROPOSAL_CREATE: + return new ProposalCreateService(this.transactionService); case TangleRequestType.PROPOSAL_APPROVE: - case TangleRequestType.PROPOSAL_REJECT: { - const service = new ProposalApprovalService(this.transactionService); - return await service.handleProposalApproval(owner, request); - } - case TangleRequestType.PROPOSAL_VOTE: { - const service = new ProposalVoteService(this.transactionService); - return await service.handleVoteOnProposal(owner, request, tran, tranEntry); - } - case TangleRequestType.SPACE_JOIN: { - const service = new SpaceJoinService(this.transactionService); - return await service.handleSpaceJoinRequest(owner, request); - } + return new ProposalApprovalService(this.transactionService); + case TangleRequestType.PROPOSAL_REJECT: + return new ProposalApprovalService(this.transactionService); + case TangleRequestType.PROPOSAL_VOTE: + return new ProposalVoteService(this.transactionService); + case TangleRequestType.SPACE_JOIN: + return new SpaceJoinService(this.transactionService); case TangleRequestType.SPACE_ADD_GUARDIAN: - case TangleRequestType.SPACE_REMOVE_GUARDIAN: { - const service = new SpaceGuardianService(this.transactionService); - return await service.handleEditGuardianRequest(owner, request); - } - case TangleRequestType.SPACE_ACCEPT_MEMBER: { - const service = new SpaceAcceptMemberService(this.transactionService); - return await service.handleAcceptMemberRequest(owner, request); - } - case TangleRequestType.SPACE_BLOCK_MEMBER: { - const service = new SpaceBlockMemberService(this.transactionService); - return await service.handleBlockMemberRequest(owner, request); - } - case TangleRequestType.SPACE_DECLINE_MEMBER: { - const service = new SpaceDeclineMemberService(this.transactionService); - return await service.handleDeclineMemberRequest(owner, request); - } - case TangleRequestType.SPACE_LEAVE: { - const service = new SpaceLeaveService(this.transactionService); - return await service.handleLeaveSpaceRequest(owner, request); - } - case TangleRequestType.SPACE_CREATE: { - const service = new SpaceCreateService(this.transactionService); - return await service.handleSpaceCreateRequest(owner, request); - } - case TangleRequestType.MINT_METADATA_NFT: { - const service = new MintMetadataNftService(this.transactionService); - return await service.handleMetadataNftMintRequest( - order.network!, - owner, - request, - match, - tran, - tranEntry, - ); - } - + return new SpaceGuardianService(this.transactionService); + case TangleRequestType.SPACE_REMOVE_GUARDIAN: + return new SpaceGuardianService(this.transactionService); + case TangleRequestType.SPACE_ACCEPT_MEMBER: + return new SpaceAcceptMemberService(this.transactionService); + case TangleRequestType.SPACE_BLOCK_MEMBER: + return new SpaceBlockMemberService(this.transactionService); + case TangleRequestType.SPACE_DECLINE_MEMBER: + return new SpaceDeclineMemberService(this.transactionService); + case TangleRequestType.SPACE_LEAVE: + return new SpaceLeaveService(this.transactionService); + case TangleRequestType.SPACE_CREATE: + return new SpaceCreateService(this.transactionService); + case TangleRequestType.MINT_METADATA_NFT: + return new MintMetadataNftService(this.transactionService); default: throw invalidArgument(WenError.invalid_tangle_request_type); } diff --git a/packages/functions/src/services/payment/tangle-service/address/address-validation.service.ts b/packages/functions/src/services/payment/tangle-service/address/address-validation.service.ts index 34d55ae8ca..169cd03af3 100644 --- a/packages/functions/src/services/payment/tangle-service/address/address-validation.service.ts +++ b/packages/functions/src/services/payment/tangle-service/address/address-validation.service.ts @@ -2,8 +2,6 @@ import { build5Db } from '@build-5/database'; import { BaseTangleResponse, COL, - MilestoneTransaction, - MilestoneTransactionEntry, Network, Space, TRANSACTION_AUTO_EXPIRY_MS, @@ -22,19 +20,17 @@ import { assertValidationAsync } from '../../../../utils/schema.utils'; import { assertIsGuardian } from '../../../../utils/token.utils'; import { getRandomEthAddress } from '../../../../utils/wallet.utils'; import { WalletService } from '../../../wallet/wallet'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { validateAddressSchemaObject } from './AddressValidationTangleRequestSchema'; -export class TangleAddressValidationService { - constructor(readonly transactionService: TransactionService) {} - - public handeAddressValidation = async ( - tangleOrder: Transaction, - tran: MilestoneTransaction, - tranEntry: MilestoneTransactionEntry, - owner: string, - request: Record, - ): Promise => { +export class TangleAddressValidationService extends BaseService { + public handleRequest = async ({ + tangleOrder, + tran, + tranEntry, + owner, + request, + }: HandlerParams): Promise => { const params = await assertValidationAsync(validateAddressSchemaObject, request); const order = await createAddressValidationOrder( diff --git a/packages/functions/src/services/payment/tangle-service/award/award.approve.participant.service.ts b/packages/functions/src/services/payment/tangle-service/award/award.approve.participant.service.ts index b19ab9cd17..10f17b16df 100644 --- a/packages/functions/src/services/payment/tangle-service/award/award.approve.participant.service.ts +++ b/packages/functions/src/services/payment/tangle-service/award/award.approve.participant.service.ts @@ -26,16 +26,14 @@ import { invalidArgument } from '../../../../utils/error.utils'; import { assertValidationAsync } from '../../../../utils/schema.utils'; import { assertIsGuardian } from '../../../../utils/token.utils'; import { getRandomEthAddress } from '../../../../utils/wallet.utils'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { approveAwardParticipantSchemaObject } from './AwardAppParticipantTangleRequestSchema'; -export class AwardApproveParticipantService { - constructor(readonly transactionService: TransactionService) {} - - public handleApproveParticipantRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class AwardApproveParticipantService extends BaseService { + public handleRequest = async ({ + owner, + request, + }: HandlerParams): Promise => { const params = await assertValidationAsync(approveAwardParticipantSchemaObject, request); const badges: { [key: string]: string } = {}; diff --git a/packages/functions/src/services/payment/tangle-service/award/award.create.service.ts b/packages/functions/src/services/payment/tangle-service/award/award.create.service.ts index 33979ec99b..6ebdba9406 100644 --- a/packages/functions/src/services/payment/tangle-service/award/award.create.service.ts +++ b/packages/functions/src/services/payment/tangle-service/award/award.create.service.ts @@ -27,17 +27,15 @@ import { isStorageUrl } from '../../../joi/common'; import { SmrWallet } from '../../../wallet/SmrWalletService'; import { WalletService } from '../../../wallet/wallet'; import { getAwardgStorageDeposits } from '../../award/award-service'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { awardCreateSchema } from './AwardCreateTangleRequestSchema'; import { createAwardFundOrder } from './award.fund.service'; -export class AwardCreateService { - constructor(readonly transactionService: TransactionService) {} - - public handleCreateRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class AwardCreateService extends BaseService { + public handleRequest = async ({ + owner, + request, + }: HandlerParams): Promise => { const params = await assertValidationAsync(awardCreateSchema, request); const { award, owner: awardOwner } = await createAward(owner, { ...params }); diff --git a/packages/functions/src/services/payment/tangle-service/award/award.fund.service.ts b/packages/functions/src/services/payment/tangle-service/award/award.fund.service.ts index 0fc9f1ad9a..46f0d7cbbd 100644 --- a/packages/functions/src/services/payment/tangle-service/award/award.fund.service.ts +++ b/packages/functions/src/services/payment/tangle-service/award/award.fund.service.ts @@ -19,16 +19,11 @@ import { assertValidationAsync } from '../../../../utils/schema.utils'; import { assertIsGuardian } from '../../../../utils/token.utils'; import { getRandomEthAddress } from '../../../../utils/wallet.utils'; import { WalletService } from '../../../wallet/wallet'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { awardFundSchema } from './AwardFundTangleRequestSchema'; -export class AwardFundService { - constructor(readonly transactionService: TransactionService) {} - - public handleFundRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class AwardFundService extends BaseService { + public handleRequest = async ({ owner, request }: HandlerParams): Promise => { const params = await assertValidationAsync(awardFundSchema, request); const award = await getAwardForFunding(owner, params.uid); diff --git a/packages/functions/src/services/payment/tangle-service/metadataNft/mint-metadata-nft.service.ts b/packages/functions/src/services/payment/tangle-service/metadataNft/mint-metadata-nft.service.ts index e40906a6ff..43789e69ba 100644 --- a/packages/functions/src/services/payment/tangle-service/metadataNft/mint-metadata-nft.service.ts +++ b/packages/functions/src/services/payment/tangle-service/metadataNft/mint-metadata-nft.service.ts @@ -1,10 +1,7 @@ import { build5Db } from '@build-5/database'; import { COL, - MilestoneTransaction, - MilestoneTransactionEntry, MintMetadataNftTangleRequest, - Network, NftStatus, SUB_COL, Space, @@ -44,20 +41,18 @@ import { import { getRandomEthAddress } from '../../../../utils/wallet.utils'; import { SmrWallet } from '../../../wallet/SmrWalletService'; import { WalletService } from '../../../wallet/wallet'; -import { TransactionMatch, TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { metadataNftSchema } from './MetadataNftTangleRequestSchema'; -export class MintMetadataNftService { - constructor(readonly transactionService: TransactionService) {} - - public handleMetadataNftMintRequest = async ( - network: Network, - owner: string, - request: Record, - match: TransactionMatch, - tran: MilestoneTransaction, - tranEntry: MilestoneTransactionEntry, - ) => { +export class MintMetadataNftService extends BaseService { + public handleRequest = async ({ + network, + owner, + request, + match, + tran, + tranEntry, + }: HandlerParams) => { const params = await assertValidationAsync(metadataNftSchema, request); const wallet = (await WalletService.newWallet(network)) as SmrWallet; diff --git a/packages/functions/src/services/payment/tangle-service/nft/nft-bid.service.ts b/packages/functions/src/services/payment/tangle-service/nft/nft-bid.service.ts index 14fa82047a..a714411aad 100644 --- a/packages/functions/src/services/payment/tangle-service/nft/nft-bid.service.ts +++ b/packages/functions/src/services/payment/tangle-service/nft/nft-bid.service.ts @@ -1,3 +1,4 @@ +import { build5Db } from '@build-5/database'; import { BaseTangleResponse, COL, @@ -6,8 +7,6 @@ import { Entity, MIN_AMOUNT_TO_TRANSFER, Member, - MilestoneTransaction, - MilestoneTransactionEntry, Network, Nft, NftAccess, @@ -18,6 +17,7 @@ import { TransactionValidationType, WenError, } from '@build-5/interfaces'; +import { AVAILABLE_NETWORKS } from '../../../../controls/common'; import { assertMemberHasValidAddress, getAddress } from '../../../../utils/address.utils'; import { getRestrictions } from '../../../../utils/common.utils'; import { isProdEnv } from '../../../../utils/config.utils'; @@ -27,20 +27,16 @@ import { assertValidationAsync } from '../../../../utils/schema.utils'; import { getSpace } from '../../../../utils/space.utils'; import { getRandomEthAddress } from '../../../../utils/wallet.utils'; import { WalletService } from '../../../wallet/wallet'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { nftBidSchema } from './NftBidTangleRequestSchema'; -import { AVAILABLE_NETWORKS } from '../../../../controls/common'; -import { build5Db } from '@build-5/database'; - -export class TangleNftBidService { - constructor(readonly transactionService: TransactionService) {} - public handleNftBid = async ( - tran: MilestoneTransaction, - tranEntry: MilestoneTransactionEntry, - owner: string, - request: Record, - ): Promise => { +export class TangleNftBidService extends BaseService { + public handleRequest = async ({ + owner, + request, + tran, + tranEntry, + }: HandlerParams): Promise => { const params = await assertValidationAsync(nftBidSchema, request); const order = await createNftBidOrder(params.nft, owner, ''); diff --git a/packages/functions/src/services/payment/tangle-service/nft/nft-deposit.service.ts b/packages/functions/src/services/payment/tangle-service/nft/nft-deposit.service.ts index 637f771431..58cbe2b76f 100644 --- a/packages/functions/src/services/payment/tangle-service/nft/nft-deposit.service.ts +++ b/packages/functions/src/services/payment/tangle-service/nft/nft-deposit.service.ts @@ -1,9 +1,5 @@ -import { build5Db } from '@build-5/database'; import { COL, - MilestoneTransaction, - MilestoneTransactionEntry, - Network, TRANSACTION_AUTO_EXPIRY_MS, Transaction, TransactionPayloadType, @@ -14,17 +10,11 @@ import dayjs from 'dayjs'; import { dateToTimestamp } from '../../../../utils/dateTime.utils'; import { getRandomEthAddress } from '../../../../utils/wallet.utils'; import { WalletService } from '../../../wallet/wallet'; -import { TransactionService } from '../../transaction-service'; - -export class NftDepositService { - constructor(readonly transactionService: TransactionService) {} +import { BaseService, HandlerParams } from '../../base'; +import { build5Db } from '@build-5/database'; - public handleNftDeposit = async ( - network: Network, - owner: string, - tran: MilestoneTransaction, - tranEntry: MilestoneTransactionEntry, - ) => { +export class NftDepositService extends BaseService { + public handleRequest = async ({ network, owner, tran, tranEntry }: HandlerParams) => { const wallet = await WalletService.newWallet(network); const targetAddress = await wallet.getNewIotaAddressDetails(); diff --git a/packages/functions/src/services/payment/tangle-service/nft/nft-purchase.service.ts b/packages/functions/src/services/payment/tangle-service/nft/nft-purchase.service.ts index bc389eb6d4..0d138ba0f1 100644 --- a/packages/functions/src/services/payment/tangle-service/nft/nft-purchase.service.ts +++ b/packages/functions/src/services/payment/tangle-service/nft/nft-purchase.service.ts @@ -9,8 +9,6 @@ import { Entity, MIN_AMOUNT_TO_TRANSFER, Member, - MilestoneTransaction, - MilestoneTransactionEntry, Network, NetworkAddress, Nft, @@ -40,18 +38,16 @@ import { getSpace } from '../../../../utils/space.utils'; import { getRandomEthAddress } from '../../../../utils/wallet.utils'; import { assertHasAccess } from '../../../validators/access'; import { WalletService } from '../../../wallet/wallet'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { nftPurchaseSchema } from './NftPurchaseTangleRequestSchema'; -export class TangleNftPurchaseService { - constructor(readonly transactionService: TransactionService) {} - - public handleNftPurchase = async ( - tran: MilestoneTransaction, - tranEntry: MilestoneTransactionEntry, - owner: string, - request: Record, - ): Promise => { +export class TangleNftPurchaseService extends BaseService { + public handleRequest = async ({ + request, + owner, + tran, + tranEntry, + }: HandlerParams): Promise => { const params = await assertValidationAsync(nftPurchaseSchema, request); const order = await createNftPuchaseOrder(params.collection, params.nft, owner, '', true); diff --git a/packages/functions/src/services/payment/tangle-service/nft/nft-set-for-sale.service.ts b/packages/functions/src/services/payment/tangle-service/nft/nft-set-for-sale.service.ts index f279497063..880f0700b5 100644 --- a/packages/functions/src/services/payment/tangle-service/nft/nft-set-for-sale.service.ts +++ b/packages/functions/src/services/payment/tangle-service/nft/nft-set-for-sale.service.ts @@ -16,13 +16,11 @@ import { assertMemberHasValidAddress } from '../../../../utils/address.utils'; import { dateToTimestamp } from '../../../../utils/dateTime.utils'; import { invalidArgument } from '../../../../utils/error.utils'; import { assertValidationAsync } from '../../../../utils/schema.utils'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { setNftForSaleTangleSchema } from './NftSetForSaleTangleRequestSchema'; -export class TangleNftSetForSaleService { - constructor(readonly transactionService: TransactionService) {} - - public handleNftSetForSale = async (owner: string, request: Record) => { +export class TangleNftSetForSaleService extends BaseService { + public handleRequest = async ({ owner, request }: HandlerParams) => { const params = await assertValidationAsync(setNftForSaleTangleSchema, request); const memberDocRef = build5Db().doc(`${COL.MEMBER}/${owner}`); const member = await memberDocRef.get(); diff --git a/packages/functions/src/services/payment/tangle-service/proposal/ProposalApporvalService.ts b/packages/functions/src/services/payment/tangle-service/proposal/ProposalApporvalService.ts index eddea634b7..c6bc521af7 100644 --- a/packages/functions/src/services/payment/tangle-service/proposal/ProposalApporvalService.ts +++ b/packages/functions/src/services/payment/tangle-service/proposal/ProposalApporvalService.ts @@ -9,16 +9,11 @@ import { import { invalidArgument } from '../../../../utils/error.utils'; import { assertValidationAsync } from '../../../../utils/schema.utils'; import { assertIsGuardian } from '../../../../utils/token.utils'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { proposalApproveSchema } from './ProposalApproveTangleRequestSchema'; -export class ProposalApprovalService { - constructor(readonly transactionService: TransactionService) {} - - public handleProposalApproval = async ( - owner: string, - request: Record, - ): Promise => { +export class ProposalApprovalService extends BaseService { + public handleRequest = async ({ owner, request }: HandlerParams): Promise => { const params = await assertValidationAsync(proposalApproveSchema, request); const data = await getProposalApprovalData( owner, diff --git a/packages/functions/src/services/payment/tangle-service/proposal/ProposalCreateService.ts b/packages/functions/src/services/payment/tangle-service/proposal/ProposalCreateService.ts index 2dd1ef24ec..5c30b6a34d 100644 --- a/packages/functions/src/services/payment/tangle-service/proposal/ProposalCreateService.ts +++ b/packages/functions/src/services/payment/tangle-service/proposal/ProposalCreateService.ts @@ -15,16 +15,14 @@ import { invalidArgument } from '../../../../utils/error.utils'; import { assertValidationAsync } from '../../../../utils/schema.utils'; import { getTokenForSpace } from '../../../../utils/token.utils'; import { getRandomEthAddress } from '../../../../utils/wallet.utils'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { proposalCreateSchemaObject } from './ProposalCreateTangleRequestSchema'; -export class ProposalCreateService { - constructor(readonly transactionService: TransactionService) {} - - public handleProposalCreateRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class ProposalCreateService extends BaseService { + public handleRequest = async ({ + owner, + request, + }: HandlerParams): Promise => { const params = await assertValidationAsync(proposalCreateSchemaObject, request); const { proposal, proposalOwner } = await createProposal(owner, { ...params }); diff --git a/packages/functions/src/services/payment/tangle-service/proposal/voting/ProposalVoteService.ts b/packages/functions/src/services/payment/tangle-service/proposal/voting/ProposalVoteService.ts index 9f3c1aa436..05d8041cee 100644 --- a/packages/functions/src/services/payment/tangle-service/proposal/voting/ProposalVoteService.ts +++ b/packages/functions/src/services/payment/tangle-service/proposal/voting/ProposalVoteService.ts @@ -22,21 +22,19 @@ import { invalidArgument } from '../../../../../utils/error.utils'; import { assertValidationAsync } from '../../../../../utils/schema.utils'; import { getTokenForSpace } from '../../../../../utils/token.utils'; import { getRandomEthAddress } from '../../../../../utils/wallet.utils'; -import { TransactionService } from '../../../transaction-service'; +import { BaseService, HandlerParams } from '../../../base'; import { voteOnProposalSchemaObject } from './ProposalVoteTangleRequestSchema'; import { executeSimpleVoting } from './simple.voting'; import { voteWithStakedTokens } from './staked.token.voting'; import { createVoteTransactionOrder } from './token.voting'; -export class ProposalVoteService { - constructor(readonly transactionService: TransactionService) {} - - public handleVoteOnProposal = async ( - owner: string, - request: Record, - milestoneTran: MilestoneTransaction, - milestoneTranEntry: MilestoneTransactionEntry, - ): Promise => { +export class ProposalVoteService extends BaseService { + public handleRequest = async ({ + owner, + request, + tran, + tranEntry, + }: HandlerParams): Promise => { const params = await assertValidationAsync(voteOnProposalSchemaObject, request); const proposal = await getProposal(params.uid as string); @@ -58,14 +56,7 @@ export class ProposalVoteService { return { status: 'success', voteTransaction: voteTransaction.uid }; } - await this.handleTokenVoteRequest( - owner, - proposal, - [params.value], - token, - milestoneTran, - milestoneTranEntry, - ); + await this.handleTokenVoteRequest(owner, proposal, [params.value], token, tran, tranEntry); return; } diff --git a/packages/functions/src/services/payment/tangle-service/proposal/voting/staked.token.voting.ts b/packages/functions/src/services/payment/tangle-service/proposal/voting/staked.token.voting.ts index fe63f0d81c..26a794cc7a 100644 --- a/packages/functions/src/services/payment/tangle-service/proposal/voting/staked.token.voting.ts +++ b/packages/functions/src/services/payment/tangle-service/proposal/voting/staked.token.voting.ts @@ -1,9 +1,9 @@ -import { build5Db, ITransaction } from '@build-5/database'; +import { ITransaction, build5Db } from '@build-5/database'; import { COL, Proposal, - Stake, SUB_COL, + Stake, TokenDistribution, Transaction, WenError, diff --git a/packages/functions/src/services/payment/tangle-service/space/SpaceAcceptMemberService.ts b/packages/functions/src/services/payment/tangle-service/space/SpaceAcceptMemberService.ts index 6df162ee52..eb99c8e307 100644 --- a/packages/functions/src/services/payment/tangle-service/space/SpaceAcceptMemberService.ts +++ b/packages/functions/src/services/payment/tangle-service/space/SpaceAcceptMemberService.ts @@ -3,16 +3,11 @@ import { BaseTangleResponse, COL, SpaceMember, SUB_COL, WenError } from '@build- import { invalidArgument } from '../../../../utils/error.utils'; import { assertValidationAsync } from '../../../../utils/schema.utils'; import { assertIsGuardian } from '../../../../utils/token.utils'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { editSpaceMemberSchemaObject } from './SpaceEditMemberTangleRequestSchema'; -export class SpaceAcceptMemberService { - constructor(readonly transactionService: TransactionService) {} - - public handleAcceptMemberRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class SpaceAcceptMemberService extends BaseService { + public handleRequest = async ({ owner, request }: HandlerParams): Promise => { const params = await assertValidationAsync(editSpaceMemberSchemaObject, request); const { spaceMember, space } = await acceptSpaceMember(owner, params.uid, params.member); diff --git a/packages/functions/src/services/payment/tangle-service/space/SpaceBlockMemberService.ts b/packages/functions/src/services/payment/tangle-service/space/SpaceBlockMemberService.ts index 99da707980..b4a14a84f5 100644 --- a/packages/functions/src/services/payment/tangle-service/space/SpaceBlockMemberService.ts +++ b/packages/functions/src/services/payment/tangle-service/space/SpaceBlockMemberService.ts @@ -3,16 +3,11 @@ import { BaseTangleResponse, COL, Space, SUB_COL, WenError } from '@build-5/inte import { invalidArgument } from '../../../../utils/error.utils'; import { assertValidationAsync } from '../../../../utils/schema.utils'; import { assertIsGuardian } from '../../../../utils/token.utils'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { editSpaceMemberSchemaObject } from './SpaceEditMemberTangleRequestSchema'; -export class SpaceBlockMemberService { - constructor(readonly transactionService: TransactionService) {} - - public handleBlockMemberRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class SpaceBlockMemberService extends BaseService { + public handleRequest = async ({ owner, request }: HandlerParams): Promise => { const params = await assertValidationAsync(editSpaceMemberSchemaObject, request); const member = params.member; diff --git a/packages/functions/src/services/payment/tangle-service/space/SpaceCreateService.ts b/packages/functions/src/services/payment/tangle-service/space/SpaceCreateService.ts index caac32b3d7..d4cfe0eaba 100644 --- a/packages/functions/src/services/payment/tangle-service/space/SpaceCreateService.ts +++ b/packages/functions/src/services/payment/tangle-service/space/SpaceCreateService.ts @@ -9,13 +9,11 @@ import { spaceToIpfsMetadata } from '../../../../utils/space.utils'; import { getRandomEthAddress } from '../../../../utils/wallet.utils'; import { isStorageUrl } from '../../../joi/common'; import { WalletService } from '../../../wallet/wallet'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { createSpaceSchemaObject } from './SpaceCreateTangleRequestSchema'; -export class SpaceCreateService { - constructor(readonly transactionService: TransactionService) {} - - public handleSpaceCreateRequest = async (owner: string, request: Record) => { +export class SpaceCreateService extends BaseService { + public handleRequest = async ({ owner, request }: HandlerParams) => { await assertValidationAsync(createSpaceSchemaObject, request); const { space, guardian, member } = await getCreateSpaceData(owner, request); diff --git a/packages/functions/src/services/payment/tangle-service/space/SpaceDeclineMemberService.ts b/packages/functions/src/services/payment/tangle-service/space/SpaceDeclineMemberService.ts index 34c738a7b8..2387979d70 100644 --- a/packages/functions/src/services/payment/tangle-service/space/SpaceDeclineMemberService.ts +++ b/packages/functions/src/services/payment/tangle-service/space/SpaceDeclineMemberService.ts @@ -1,17 +1,12 @@ -import { build5Db } from '@build-5/database'; import { BaseTangleResponse, COL, SUB_COL } from '@build-5/interfaces'; import { assertValidationAsync } from '../../../../utils/schema.utils'; import { assertIsGuardian } from '../../../../utils/token.utils'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { editSpaceMemberSchemaObject } from './SpaceEditMemberTangleRequestSchema'; +import { build5Db } from '@build-5/database'; -export class SpaceDeclineMemberService { - constructor(readonly transactionService: TransactionService) {} - - public handleDeclineMemberRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class SpaceDeclineMemberService extends BaseService { + public handleRequest = async ({ owner, request }: HandlerParams): Promise => { const params = await assertValidationAsync(editSpaceMemberSchemaObject, request); await assertIsGuardian(params.uid, owner); diff --git a/packages/functions/src/services/payment/tangle-service/space/SpaceGuardianService.ts b/packages/functions/src/services/payment/tangle-service/space/SpaceGuardianService.ts index b184f9dbb0..7208947a06 100644 --- a/packages/functions/src/services/payment/tangle-service/space/SpaceGuardianService.ts +++ b/packages/functions/src/services/payment/tangle-service/space/SpaceGuardianService.ts @@ -22,16 +22,14 @@ import { invalidArgument } from '../../../../utils/error.utils'; import { assertValidationAsync } from '../../../../utils/schema.utils'; import { assertIsGuardian } from '../../../../utils/token.utils'; import { getRandomEthAddress } from '../../../../utils/wallet.utils'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { editSpaceMemberSchemaObject } from './SpaceEditMemberTangleRequestSchema'; -export class SpaceGuardianService { - constructor(readonly transactionService: TransactionService) {} - - public handleEditGuardianRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class SpaceGuardianService extends BaseService { + public handleRequest = async ({ + owner, + request, + }: HandlerParams): Promise => { const type = request.requestType == TangleRequestType.SPACE_ADD_GUARDIAN ? ProposalType.ADD_GUARDIAN diff --git a/packages/functions/src/services/payment/tangle-service/space/SpaceJoinService.ts b/packages/functions/src/services/payment/tangle-service/space/SpaceJoinService.ts index 68007d785b..f4b40593e8 100644 --- a/packages/functions/src/services/payment/tangle-service/space/SpaceJoinService.ts +++ b/packages/functions/src/services/payment/tangle-service/space/SpaceJoinService.ts @@ -14,16 +14,11 @@ import { invalidArgument } from '../../../../utils/error.utils'; import { assertValidationAsync } from '../../../../utils/schema.utils'; import { getTokenForSpace } from '../../../../utils/token.utils'; import { getStakeForType } from '../../../stake.service'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { joinSpaceSchema } from './SpaceJoinTangleRequestSchema'; -export class SpaceJoinService { - constructor(readonly transactionService: TransactionService) {} - - public handleSpaceJoinRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class SpaceJoinService extends BaseService { + public handleRequest = async ({ owner, request }: HandlerParams): Promise => { const params = await assertValidationAsync(joinSpaceSchema, request); const spaceDocRef = build5Db().doc(`${COL.SPACE}/${params.uid}`); @@ -104,9 +99,8 @@ export const getJoinSpaceData = async (owner: string, space: Space) => { const assertMemberHasEnoughStakedTokens = async (space: Space, member: string) => { const token = await getTokenForSpace(space.uid); - const distributionDocRef = build5Db().doc( - `${COL.TOKEN}/${token?.uid}/${SUB_COL.DISTRIBUTION}/${member}`, - ); + const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${token?.uid}`); + const distributionDocRef = tokenDocRef.collection(SUB_COL.DISTRIBUTION).doc(member); const distribution = await distributionDocRef.get(); const stakeValue = getStakeForType(distribution, StakeType.DYNAMIC); if (stakeValue < (space.minStakedValue || 0)) { diff --git a/packages/functions/src/services/payment/tangle-service/space/SpaceLeaveService.ts b/packages/functions/src/services/payment/tangle-service/space/SpaceLeaveService.ts index 7a9cb38792..bf57e062a1 100644 --- a/packages/functions/src/services/payment/tangle-service/space/SpaceLeaveService.ts +++ b/packages/functions/src/services/payment/tangle-service/space/SpaceLeaveService.ts @@ -2,16 +2,11 @@ import { build5Db } from '@build-5/database'; import { BaseTangleResponse, COL, Space, SUB_COL, WenError } from '@build-5/interfaces'; import { invalidArgument } from '../../../../utils/error.utils'; import { assertValidationAsync } from '../../../../utils/schema.utils'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { leaveSpaceSchema } from './SpaceLeaveTangleRequestSchema'; -export class SpaceLeaveService { - constructor(readonly transactionService: TransactionService) {} - - public handleLeaveSpaceRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class SpaceLeaveService extends BaseService { + public handleRequest = async ({ owner, request }: HandlerParams): Promise => { const params = await assertValidationAsync(leaveSpaceSchema, request); const { space, member } = await getLeaveSpaceData(owner, params.uid); diff --git a/packages/functions/src/services/payment/tangle-service/token/stake.service.ts b/packages/functions/src/services/payment/tangle-service/token/stake.service.ts index 538ecd21b0..9c131f095f 100644 --- a/packages/functions/src/services/payment/tangle-service/token/stake.service.ts +++ b/packages/functions/src/services/payment/tangle-service/token/stake.service.ts @@ -1,8 +1,6 @@ import { build5Db } from '@build-5/database'; import { COL, - MilestoneTransaction, - MilestoneTransactionEntry, StakeType, TRANSACTION_AUTO_EXPIRY_MS, Transaction, @@ -23,18 +21,11 @@ import { getTokenBySymbol } from '../../../../utils/token.utils'; import { getRandomEthAddress } from '../../../../utils/wallet.utils'; import { SmrWallet } from '../../../wallet/SmrWalletService'; import { WalletService } from '../../../wallet/wallet'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { depositStakeSchemaObject } from './TokenStakeTangleRequestSchema'; -export class TangleStakeService { - constructor(readonly transactionService: TransactionService) {} - - public handleStaking = async ( - tran: MilestoneTransaction, - tranEntry: MilestoneTransactionEntry, - owner: string, - request: Record, - ) => { +export class TangleStakeService extends BaseService { + public handleRequest = async ({ owner, request, tran, tranEntry }: HandlerParams) => { const params = await assertValidationAsync(depositStakeSchemaObject, request); const order = await createStakeOrder( diff --git a/packages/functions/src/services/payment/tangle-service/token/token-claim.service.ts b/packages/functions/src/services/payment/tangle-service/token/token-claim.service.ts index aa4f42875e..a2c8ee870a 100644 --- a/packages/functions/src/services/payment/tangle-service/token/token-claim.service.ts +++ b/packages/functions/src/services/payment/tangle-service/token/token-claim.service.ts @@ -26,16 +26,11 @@ import { getTokenBySymbol, getUnclaimedDrops } from '../../../../utils/token.uti import { getRandomEthAddress } from '../../../../utils/wallet.utils'; import { SmrWallet } from '../../../wallet/SmrWalletService'; import { WalletService } from '../../../wallet/wallet'; -import { TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { tokenClaimSchema } from './TokenClaimTangleRequestSchema'; -export class TangleTokenClaimService { - constructor(readonly transactionService: TransactionService) {} - - public handleMintedTokenAirdropRequest = async ( - owner: string, - request: Record, - ): Promise => { +export class TangleTokenClaimService extends BaseService { + public handleRequest = async ({ owner, request }: HandlerParams): Promise => { const params = await assertValidationAsync(tokenClaimSchema, request); const order = await createMintedTokenAirdropCalimOrder(owner, params.symbol); this.transactionService.push({ diff --git a/packages/functions/src/services/payment/tangle-service/token/token-trade.service.ts b/packages/functions/src/services/payment/tangle-service/token/token-trade.service.ts index 848f523abb..afe88d73ea 100644 --- a/packages/functions/src/services/payment/tangle-service/token/token-trade.service.ts +++ b/packages/functions/src/services/payment/tangle-service/token/token-trade.service.ts @@ -3,8 +3,6 @@ import { COL, DEFAULT_NETWORK, Member, - MilestoneTransaction, - MilestoneTransactionEntry, Network, SUB_COL, TRANSACTION_MAX_EXPIRY_MS, @@ -41,21 +39,19 @@ import { import { getRandomEthAddress } from '../../../../utils/wallet.utils'; import { SmrWallet } from '../../../wallet/SmrWalletService'; import { WalletService } from '../../../wallet/wallet'; -import { TransactionMatch, TransactionService } from '../../transaction-service'; +import { BaseService, HandlerParams } from '../../base'; import { tradeMintedTokenSchema } from './TokenTradeTangleRequestSchema'; -export class TangleTokenTradeService { - constructor(readonly transactionService: TransactionService) {} - - public handleTokenTradeTangleRequest = async ( - match: TransactionMatch, - payment: Transaction, - tran: MilestoneTransaction, - tranEntry: MilestoneTransactionEntry, - owner: string, - request: Record, - build5Transaction?: Transaction, - ) => { +export class TangleTokenTradeService extends BaseService { + public handleRequest = async ({ + match, + payment, + tran, + tranEntry, + owner, + request, + build5Tran, + }: HandlerParams) => { const type = request.requestType === TransactionPayloadType.BUY_TOKEN ? TokenTradeOrderType.BUY @@ -98,7 +94,7 @@ export class TangleTokenTradeService { if (params.type === TokenTradeOrderType.SELL && token?.status === TokenStatus.BASE) { this.transactionService.createTangleCredit( - payment, + payment!, match, { amount: tradeOrderTransaction.payload.amount, @@ -115,8 +111,7 @@ export class TangleTokenTradeService { tranEntry, TransactionPayloadType.TANGLE_TRANSFER, tranEntry.outputId, - build5Transaction?.payload?.expiresOn || - dateToTimestamp(dayjs().add(TRANSACTION_MAX_EXPIRY_MS, 'ms')), + build5Tran?.payload?.expiresOn || dateToTimestamp(dayjs().add(TRANSACTION_MAX_EXPIRY_MS)), ); return; }; diff --git a/packages/functions/src/services/payment/token/import-minted-token.service.ts b/packages/functions/src/services/payment/token/import-minted-token.service.ts index b2f8d997aa..0ed840fb37 100644 --- a/packages/functions/src/services/payment/token/import-minted-token.service.ts +++ b/packages/functions/src/services/payment/token/import-minted-token.service.ts @@ -27,12 +27,11 @@ import { migrateUriToSotrage, uriToUrl } from '../../../utils/media.utils'; import { isAliasGovernor } from '../../../utils/token-minting-utils/alias.utils'; import { SmrWallet } from '../../wallet/SmrWalletService'; import { WalletService } from '../../wallet/wallet'; -import { TransactionMatch, TransactionService } from '../transaction-service'; +import { BaseService, HandlerParams } from '../base'; +import { TransactionMatch } from '../transaction-service'; -export class ImportMintedTokenService { - constructor(readonly transactionService: TransactionService) {} - - public handleMintedTokenImport = async (order: Transaction, match: TransactionMatch) => { +export class ImportMintedTokenService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { let error: { [key: string]: unknown } = {}; try { const tokenId = order.payload.tokenId!; diff --git a/packages/functions/src/services/payment/token/token-airdrop-claim.service.ts b/packages/functions/src/services/payment/token/token-airdrop-claim.service.ts new file mode 100644 index 0000000000..509c100859 --- /dev/null +++ b/packages/functions/src/services/payment/token/token-airdrop-claim.service.ts @@ -0,0 +1,14 @@ +import { TransactionPayloadType } from '@build-5/interfaces'; +import { BaseService, HandlerParams } from '../base'; + +export class TokenAirdropClaimService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { + const payment = await this.transactionService.createPayment(order, match); + await this.transactionService.createCredit( + TransactionPayloadType.PRE_MINTED_CLAIM, + payment, + match, + ); + this.transactionService.markAsReconciled(order, match.msgId); + }; +} diff --git a/packages/functions/src/services/payment/token/token-mint-service.ts b/packages/functions/src/services/payment/token/token-mint.service.ts similarity index 82% rename from packages/functions/src/services/payment/token/token-mint-service.ts rename to packages/functions/src/services/payment/token/token-mint.service.ts index 619ff0cc6e..2d3316563e 100644 --- a/packages/functions/src/services/payment/token/token-mint-service.ts +++ b/packages/functions/src/services/payment/token/token-mint.service.ts @@ -1,13 +1,11 @@ import { build5Db } from '@build-5/database'; -import { COL, Token, TokenStatus, Transaction, TransactionPayloadType } from '@build-5/interfaces'; +import { COL, Token, TokenStatus, TransactionPayloadType } from '@build-5/interfaces'; import dayjs from 'dayjs'; import { get } from 'lodash'; -import { TransactionMatch, TransactionService } from '../transaction-service'; +import { BaseService, HandlerParams } from '../base'; -export class TokenMintService { - constructor(readonly transactionService: TransactionService) {} - - public handleMintingRequest = async (order: Transaction, match: TransactionMatch) => { +export class TokenMintService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${order.payload.token}`); const token = await this.transactionService.get(tokenDocRef); diff --git a/packages/functions/src/services/payment/token/token-minted-airdrop.service.ts b/packages/functions/src/services/payment/token/token-minted-airdrop.service.ts new file mode 100644 index 0000000000..42df057148 --- /dev/null +++ b/packages/functions/src/services/payment/token/token-minted-airdrop.service.ts @@ -0,0 +1,76 @@ +import { build5Db, getSnapshot } from '@build-5/database'; +import { + COL, + SUB_COL, + Token, + TokenDrop, + TokenDropStatus, + TransactionPayloadType, +} from '@build-5/interfaces'; +import { get, last } from 'lodash'; +import { BaseService, HandlerParams } from '../base'; + +export class TokenMintedAirdropService extends BaseService { + public handleRequest = async ({ order, match, tranEntry }: HandlerParams) => { + const payment = await this.transactionService.createPayment(order, match); + const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${order.payload.token}`); + const token = await tokenDocRef.get(); + const tokensSent = (tranEntry.nativeTokens || []).reduce( + (acc, act) => (act.id === token.mintingData?.tokenId ? acc + Number(act.amount) : acc), + 0, + ); + const tokensExpected = get(order, 'payload.totalAirdropCount', 0); + + if (tokensSent !== tokensExpected || (tranEntry.nativeTokens || []).length > 1) { + await this.transactionService.createCredit( + TransactionPayloadType.INVALID_AMOUNT, + payment, + match, + ); + return; + } + + let lastDocId = ''; + do { + const lastDoc = await getSnapshot(COL.AIRDROP, lastDocId); + const snap = await build5Db() + .collection(COL.AIRDROP) + .where('orderId', '==', order.uid) + .limit(250) + .startAfter(lastDoc) + .get(); + lastDocId = last(snap)?.uid || ''; + + const batch = build5Db().batch(); + snap.forEach((airdrop) => { + const distributionDocRef = build5Db() + .collection(COL.TOKEN) + .doc(airdrop.token) + .collection(SUB_COL.DISTRIBUTION) + .doc(airdrop.member); + + batch.set( + distributionDocRef, + { + parentId: airdrop.token, + parentCol: COL.TOKEN, + uid: airdrop.member, + totalUnclaimedAirdrop: build5Db().inc(airdrop.count), + }, + true, + ); + const docRef = build5Db().doc(`${COL.AIRDROP}/${airdrop.uid}`); + batch.update(docRef, { status: TokenDropStatus.UNCLAIMED }); + }); + await batch.commit(); + } while (lastDocId); + + this.transactionService.markAsReconciled(order, match.msgId); + + this.transactionService.push({ + ref: build5Db().doc(`${COL.TRANSACTION}/${order.uid}`), + data: { 'payload.amount': tranEntry.amount }, + action: 'update', + }); + }; +} diff --git a/packages/functions/src/services/payment/token/minted-token-claim.ts b/packages/functions/src/services/payment/token/token-minted-claim.service.ts similarity index 51% rename from packages/functions/src/services/payment/token/minted-token-claim.ts rename to packages/functions/src/services/payment/token/token-minted-claim.service.ts index 123bbd0295..53b0171413 100644 --- a/packages/functions/src/services/payment/token/minted-token-claim.ts +++ b/packages/functions/src/services/payment/token/token-minted-claim.service.ts @@ -1,10 +1,8 @@ -import { Transaction, TransactionPayloadType } from '@build-5/interfaces'; -import { TransactionMatch, TransactionService } from '../transaction-service'; +import { TransactionPayloadType } from '@build-5/interfaces'; +import { BaseService, HandlerParams } from '../base'; -export class MintedTokenClaimService { - constructor(readonly transactionService: TransactionService) {} - - public handleClaimRequest = async (order: Transaction, match: TransactionMatch) => { +export class MintedTokenClaimService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { const payment = await this.transactionService.createPayment(order, match); if (order.payload.amount !== match.to.amount) { await this.transactionService.createCredit( diff --git a/packages/functions/src/services/payment/token/token-purchase.service.ts b/packages/functions/src/services/payment/token/token-purchase.service.ts new file mode 100644 index 0000000000..1d17847907 --- /dev/null +++ b/packages/functions/src/services/payment/token/token-purchase.service.ts @@ -0,0 +1,80 @@ +import { build5Db } from '@build-5/database'; +import { + COL, + SUB_COL, + Token, + TokenDistribution, + TokenStatus, + Transaction, + TransactionPayloadType, +} from '@build-5/interfaces'; +import bigDecimal from 'js-big-decimal'; +import { getBoughtByMemberDiff, getTotalPublicSupply } from '../../../utils/token.utils'; +import { BaseService, HandlerParams } from '../base'; +import { TransactionMatch } from '../transaction-service'; + +export class TokenPurchaseService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { + const payment = await this.transactionService.createPayment(order, match); + await this.updateTokenDistribution(order, match, payment); + }; + + private async updateTokenDistribution( + order: Transaction, + tran: TransactionMatch, + payment: Transaction, + ) { + const tokenRef = build5Db().doc(`${COL.TOKEN}/${order.payload.token}`); + const distributionRef = tokenRef.collection(SUB_COL.DISTRIBUTION).doc(order.member!); + + const token = await this.transactionService.get(tokenRef); + if (token.status !== TokenStatus.AVAILABLE) { + await this.transactionService.createCredit( + TransactionPayloadType.DATA_NO_LONGER_VALID, + payment, + tran, + ); + return; + } + + const distribution = await this.transactionService.transaction.get( + distributionRef, + ); + const currentTotalDeposit = Number( + bigDecimal.add(distribution?.totalDeposit || 0, tran.to.amount), + ); + const boughtByMemberDiff = getBoughtByMemberDiff( + distribution?.totalDeposit || 0, + currentTotalDeposit, + token.pricePerToken, + ); + + const tokenUpdateData = { + totalDeposit: build5Db().inc(tran.to.amount), + tokensOrdered: build5Db().inc(boughtByMemberDiff), + }; + const tokensOrdered = Number(bigDecimal.add(token.tokensOrdered, boughtByMemberDiff)); + const totalPublicSupply = getTotalPublicSupply(token); + + this.transactionService.push({ + ref: tokenRef, + data: + tokensOrdered >= totalPublicSupply && token.autoProcessAt100Percent + ? { ...tokenUpdateData, status: TokenStatus.PROCESSING } + : tokenUpdateData, + action: 'update', + }); + + this.transactionService.push({ + ref: distributionRef, + data: { + uid: order.member, + totalDeposit: build5Db().inc(tran.to.amount), + parentId: order.payload.token, + parentCol: COL.TOKEN, + }, + action: 'set', + merge: true, + }); + } +} diff --git a/packages/functions/src/services/payment/token/token-service.ts b/packages/functions/src/services/payment/token/token-service.ts deleted file mode 100644 index de2212f809..0000000000 --- a/packages/functions/src/services/payment/token/token-service.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { build5Db, getSnapshot } from '@build-5/database'; -import { - COL, - DEFAULT_NETWORK, - MilestoneTransactionEntry, - NativeToken, - SUB_COL, - TRANSACTION_MAX_EXPIRY_MS, - Token, - TokenDistribution, - TokenDrop, - TokenDropStatus, - TokenStatus, - TokenTradeOrder, - TokenTradeOrderStatus, - TokenTradeOrderType, - Transaction, - TransactionPayloadType, - getNetworkPair, -} from '@build-5/interfaces'; -import dayjs from 'dayjs'; -import bigDecimal from 'js-big-decimal'; -import { get, head, last } from 'lodash'; -import { dateToTimestamp } from '../../../utils/dateTime.utils'; -import { getBoughtByMemberDiff, getTotalPublicSupply } from '../../../utils/token.utils'; -import { getRandomEthAddress } from '../../../utils/wallet.utils'; -import { TransactionMatch, TransactionService } from '../transaction-service'; - -export class TokenService { - constructor(readonly transactionService: TransactionService) {} - - public async handleTokenPurchaseRequest(order: Transaction, match: TransactionMatch) { - const payment = await this.transactionService.createPayment(order, match); - await this.updateTokenDistribution(order, match, payment); - } - - public async handleTokenAirdropClaim(order: Transaction, match: TransactionMatch) { - const payment = await this.transactionService.createPayment(order, match); - await this.transactionService.createCredit( - TransactionPayloadType.PRE_MINTED_CLAIM, - payment, - match, - ); - this.transactionService.markAsReconciled(order, match.msgId); - } - - public async handleMintedTokenAirdrop( - order: Transaction, - tranOutput: MilestoneTransactionEntry, - match: TransactionMatch, - ) { - const payment = await this.transactionService.createPayment(order, match); - const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${order.payload.token}`); - const token = await tokenDocRef.get(); - const tokensSent = (tranOutput.nativeTokens || []).reduce( - (acc, act) => (act.id === token.mintingData?.tokenId ? acc + Number(act.amount) : acc), - 0, - ); - const tokensExpected = get(order, 'payload.totalAirdropCount', 0); - - if (tokensSent !== tokensExpected || (tranOutput.nativeTokens || []).length > 1) { - await this.transactionService.createCredit( - TransactionPayloadType.INVALID_AMOUNT, - payment, - match, - ); - return; - } - - let lastDocId = ''; - do { - const lastDoc = await getSnapshot(COL.AIRDROP, lastDocId); - const snap = await build5Db() - .collection(COL.AIRDROP) - .where('orderId', '==', order.uid) - .limit(250) - .startAfter(lastDoc) - .get(); - lastDocId = last(snap)?.uid || ''; - - const batch = build5Db().batch(); - snap.forEach((airdrop) => { - const distributionDocRef = build5Db() - .collection(COL.TOKEN) - .doc(airdrop.token) - .collection(SUB_COL.DISTRIBUTION) - .doc(airdrop.member); - - batch.set( - distributionDocRef, - { - parentId: airdrop.token, - parentCol: COL.TOKEN, - uid: airdrop.member, - totalUnclaimedAirdrop: build5Db().inc(airdrop.count), - }, - true, - ); - const docRef = build5Db().doc(`${COL.AIRDROP}/${airdrop.uid}`); - batch.update(docRef, { status: TokenDropStatus.UNCLAIMED }); - }); - await batch.commit(); - } while (lastDocId); - - this.transactionService.markAsReconciled(order, match.msgId); - - this.transactionService.push({ - ref: build5Db().doc(`${COL.TRANSACTION}/${order.uid}`), - data: { 'payload.amount': tranOutput.amount }, - action: 'update', - }); - } - - public async handleTokenTradeRequest( - order: Transaction, - tran: MilestoneTransactionEntry, - match: TransactionMatch, - build5Transaction?: Transaction, - ) { - const payment = await this.transactionService.createPayment(order, match); - - const nativeTokenId = head(order.payload.nativeTokens as NativeToken[])?.id; - const nativeTokens = nativeTokenId - ? Number(tran.nativeTokens?.find((n) => n.id === nativeTokenId)?.amount || 0) - : 0; - if (nativeTokenId && (!nativeTokens || (tran.nativeTokens?.length || 0) > 1)) { - await this.transactionService.createCredit( - TransactionPayloadType.INVALID_AMOUNT, - payment, - match, - ); - return; - } - this.transactionService.markAsReconciled(order, match.msgId); - - await this.createDistributionDocRef(order.payload.token!, order.member!); - const token = await build5Db().doc(`${COL.TOKEN}/${order.payload.token}`).get(); - const network = order.network || DEFAULT_NETWORK; - const data = { - uid: getRandomEthAddress(), - owner: order.member, - token: token.uid, - tokenStatus: token.status, - type: - order.payload.type === TransactionPayloadType.SELL_TOKEN - ? TokenTradeOrderType.SELL - : TokenTradeOrderType.BUY, - count: nativeTokens || get(order, 'payload.count', 0), - price: get(order, 'payload.price', 0), - totalDeposit: nativeTokens || order.payload.amount, - balance: nativeTokens || order.payload.amount, - fulfilled: 0, - status: TokenTradeOrderStatus.ACTIVE, - orderTransactionId: order.uid, - paymentTransactionId: payment.uid, - expiresAt: - build5Transaction?.payload?.expiresOn || - dateToTimestamp(dayjs().add(TRANSACTION_MAX_EXPIRY_MS, 'ms')), - sourceNetwork: network, - targetNetwork: token.status === TokenStatus.BASE ? getNetworkPair(network) : network, - }; - - const ref = build5Db().doc(`${COL.TOKEN_MARKET}/${data.uid}`); - this.transactionService.push({ ref, data, action: 'set' }); - - if ( - order.payload.type === TransactionPayloadType.SELL_TOKEN && - token.status === TokenStatus.MINTED - ) { - const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${order.uid}`); - this.transactionService.push({ - ref: orderDocRef, - data: { 'payload.amount': match.to.amount }, - action: 'update', - }); - } - } - - private async updateTokenDistribution( - order: Transaction, - tran: TransactionMatch, - payment: Transaction, - ) { - const tokenRef = build5Db().doc(`${COL.TOKEN}/${order.payload.token}`); - const distributionRef = tokenRef.collection(SUB_COL.DISTRIBUTION).doc(order.member!); - - const token = await this.transactionService.get(tokenRef); - if (token.status !== TokenStatus.AVAILABLE) { - await this.transactionService.createCredit( - TransactionPayloadType.DATA_NO_LONGER_VALID, - payment, - tran, - ); - return; - } - - const distribution = await this.transactionService.transaction.get( - distributionRef, - ); - const currentTotalDeposit = Number( - bigDecimal.add(distribution?.totalDeposit || 0, tran.to.amount), - ); - const boughtByMemberDiff = getBoughtByMemberDiff( - distribution?.totalDeposit || 0, - currentTotalDeposit, - token.pricePerToken, - ); - - const tokenUpdateData = { - totalDeposit: build5Db().inc(tran.to.amount), - tokensOrdered: build5Db().inc(boughtByMemberDiff), - }; - const tokensOrdered = Number(bigDecimal.add(token.tokensOrdered, boughtByMemberDiff)); - const totalPublicSupply = getTotalPublicSupply(token); - - this.transactionService.push({ - ref: tokenRef, - data: - tokensOrdered >= totalPublicSupply && token.autoProcessAt100Percent - ? { ...tokenUpdateData, status: TokenStatus.PROCESSING } - : tokenUpdateData, - action: 'update', - }); - - this.transactionService.push({ - ref: distributionRef, - data: { - uid: order.member, - totalDeposit: build5Db().inc(tran.to.amount), - parentId: order.payload.token, - parentCol: COL.TOKEN, - }, - action: 'set', - merge: true, - }); - } - - private createDistributionDocRef = async (token: string, member: string) => { - const distributionDocRef = build5Db().doc( - `${COL.TOKEN}/${token}/${SUB_COL.DISTRIBUTION}/${member}`, - ); - const distributionDoc = await this.transactionService.transaction.get(distributionDocRef); - if (!distributionDoc) { - const data = { - uid: member, - parentId: token, - parentCol: COL.TOKEN, - }; - this.transactionService.push({ - ref: distributionDocRef, - data, - action: 'set', - merge: true, - }); - } - }; -} diff --git a/packages/functions/src/services/payment/token/token-trade.service.ts b/packages/functions/src/services/payment/token/token-trade.service.ts new file mode 100644 index 0000000000..188d8e4a18 --- /dev/null +++ b/packages/functions/src/services/payment/token/token-trade.service.ts @@ -0,0 +1,102 @@ +import { build5Db } from '@build-5/database'; +import { + COL, + DEFAULT_NETWORK, + NativeToken, + SUB_COL, + TRANSACTION_MAX_EXPIRY_MS, + Token, + TokenStatus, + TokenTradeOrder, + TokenTradeOrderStatus, + TokenTradeOrderType, + TransactionPayloadType, + getNetworkPair, +} from '@build-5/interfaces'; +import dayjs from 'dayjs'; +import { get, head } from 'lodash'; +import { dateToTimestamp } from '../../../utils/dateTime.utils'; +import { getRandomEthAddress } from '../../../utils/wallet.utils'; +import { BaseService, HandlerParams } from '../base'; + +export class TokenTradeService extends BaseService { + public handleRequest = async ({ order, match, tranEntry, build5Tran }: HandlerParams) => { + const payment = await this.transactionService.createPayment(order, match); + + const nativeTokenId = head(order.payload.nativeTokens as NativeToken[])?.id; + const nativeTokens = nativeTokenId + ? Number(tranEntry.nativeTokens?.find((n) => n.id === nativeTokenId)?.amount || 0) + : 0; + if (nativeTokenId && (!nativeTokens || (tranEntry.nativeTokens?.length || 0) > 1)) { + await this.transactionService.createCredit( + TransactionPayloadType.INVALID_AMOUNT, + payment, + match, + ); + return; + } + this.transactionService.markAsReconciled(order, match.msgId); + + await this.createDistributionDocRef(order.payload.token!, order.member!); + const token = await build5Db().doc(`${COL.TOKEN}/${order.payload.token}`).get(); + const network = order.network || DEFAULT_NETWORK; + const data = { + uid: getRandomEthAddress(), + owner: order.member, + token: token.uid, + tokenStatus: token.status, + type: + order.payload.type === TransactionPayloadType.SELL_TOKEN + ? TokenTradeOrderType.SELL + : TokenTradeOrderType.BUY, + count: nativeTokens || get(order, 'payload.count', 0), + price: get(order, 'payload.price', 0), + totalDeposit: nativeTokens || order.payload.amount, + balance: nativeTokens || order.payload.amount, + fulfilled: 0, + status: TokenTradeOrderStatus.ACTIVE, + orderTransactionId: order.uid, + paymentTransactionId: payment.uid, + expiresAt: + build5Tran?.payload?.expiresOn || + dateToTimestamp(dayjs().add(TRANSACTION_MAX_EXPIRY_MS, 'ms')), + sourceNetwork: network, + targetNetwork: token.status === TokenStatus.BASE ? getNetworkPair(network) : network, + }; + + const ref = build5Db().doc(`${COL.TOKEN_MARKET}/${data.uid}`); + this.transactionService.push({ ref, data, action: 'set' }); + + if ( + order.payload.type === TransactionPayloadType.SELL_TOKEN && + token.status === TokenStatus.MINTED + ) { + const orderDocRef = build5Db().doc(`${COL.TRANSACTION}/${order.uid}`); + this.transactionService.push({ + ref: orderDocRef, + data: { 'payload.amount': match.to.amount }, + action: 'update', + }); + } + }; + + private createDistributionDocRef = async (token: string, member: string) => { + const distributionDocRef = build5Db().doc( + `${COL.TOKEN}/${token}/${SUB_COL.DISTRIBUTION}/${member}`, + ); + const distributionDoc = await this.transactionService.transaction.get(distributionDocRef); + if (!distributionDoc) { + const data = { + uid: member, + parentId: token, + parentCol: COL.TOKEN, + }; + this.transactionService.push({ + ref: distributionDocRef, + data, + action: 'set', + merge: true, + }); + } + }; +} diff --git a/packages/functions/src/services/payment/voting-service.ts b/packages/functions/src/services/payment/voting-service.ts index 3712187544..a2eefe45e9 100644 --- a/packages/functions/src/services/payment/voting-service.ts +++ b/packages/functions/src/services/payment/voting-service.ts @@ -11,12 +11,10 @@ import dayjs from 'dayjs'; import { get, head } from 'lodash'; import { getTokenForSpace } from '../../utils/token.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; -import { TransactionMatch, TransactionService } from './transaction-service'; +import { BaseService, HandlerParams } from './base'; -export class VotingService { - constructor(readonly transactionService: TransactionService) {} - - public async handleTokenVoteRequest(order: Transaction, match: TransactionMatch) { +export class VotingService extends BaseService { + public handleRequest = async ({ order, match }: HandlerParams) => { const payment = await this.transactionService.createPayment(order, match); this.transactionService.markAsReconciled(order, match.msgId); const token = await getTokenForSpace(order.space!); @@ -95,7 +93,7 @@ export class VotingService { action: 'set', merge: true, }); - } + }; private createVoteTransaction = ( order: Transaction, diff --git a/packages/functions/src/services/stake.service.ts b/packages/functions/src/services/stake.service.ts index ff2b6727c0..3b7a02efb8 100644 --- a/packages/functions/src/services/stake.service.ts +++ b/packages/functions/src/services/stake.service.ts @@ -1,31 +1,30 @@ -import { build5Db, ITransaction } from '@build-5/database'; +import { ITransaction, build5Db } from '@build-5/database'; import { COL, + Project, + ProjectBilling, + SUB_COL, Space, Stake, StakeType, - SUB_COL, - tiers, TokenDistribution, - tokenTradingFeeDicountPercentage, } from '@build-5/interfaces'; -import { getTokenSaleConfig, isProdEnv } from '../utils/config.utils'; -import { getSoonToken } from '../utils/token.utils'; +import { getProject } from '../utils/common.utils'; +import { getTokenSaleConfig } from '../utils/config.utils'; -export const hasStakedSoonTokens = async (member: string, type?: StakeType) => { - if (!isProdEnv()) { +export const hasStakedTokens = async (projectId: string, member: string, type?: StakeType) => { + const project = await build5Db().get(COL.PROJECT, projectId); + if (project?.config?.billing !== ProjectBilling.TOKEN_BASE) { return true; } - const soon = await getSoonToken(); - const distributionDocRef = build5Db().doc( - `${COL.TOKEN}/${soon.uid}/${SUB_COL.DISTRIBUTION}/${member}`, - ); - const distribution = (await distributionDocRef.get())!; + const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${project.config.baseTokenUid}`); + const distributionDocRef = tokenDocRef.collection(SUB_COL.DISTRIBUTION).doc(member); + const distribution = await distributionDocRef.get(); const stakeTypes = type ? [type] : Object.values(StakeType); const hasAny = stakeTypes.reduce( - (acc, act) => acc || getStakeForType(distribution, act) >= tiers[1], + (acc, act) => acc || getStakeForType(distribution, act) >= (project.config.tiers || [])[1], false, ); return hasAny; @@ -37,7 +36,13 @@ export const getStakeForType = (distribution: TokenDistribution | undefined, typ export const onStakeCreated = async (transaction: ITransaction, stake: Stake) => { const distribution = await getTokenDistribution(transaction, stake.token, stake.member); if (stake.type === StakeType.DYNAMIC) { - updateMemberTokenDiscountPercentage(transaction, distribution, stake.member, stake.value); + await updateMemberTokenDiscountPercentage( + transaction, + getProject(stake), + distribution, + stake.member, + stake.value, + ); } updateStakingMembersStats(transaction, distribution, stake.token, stake.type, stake.value); }; @@ -45,39 +50,46 @@ export const onStakeCreated = async (transaction: ITransaction, stake: Stake) => export const onStakeExpired = async (transaction: ITransaction, stake: Stake) => { const distribution = await getTokenDistribution(transaction, stake.token, stake.member); if (stake.type === StakeType.DYNAMIC) { - updateMemberTokenDiscountPercentage(transaction, distribution, stake.member, -stake.value); + await updateMemberTokenDiscountPercentage( + transaction, + getProject(stake), + distribution, + stake.member, + -stake.value, + ); await removeMemberFromSpace(transaction, distribution, stake); } updateStakingMembersStats(transaction, distribution, stake.token, stake.type, -stake.value); }; const getTokenDistribution = async (transaction: ITransaction, token: string, member: string) => { - const distirbutionDocRef = build5Db().doc( - `${COL.TOKEN}/${token}/${SUB_COL.DISTRIBUTION}/${member}`, - ); + const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${token}`); + const distirbutionDocRef = tokenDocRef.collection(SUB_COL.DISTRIBUTION).doc(member); return await transaction.get(distirbutionDocRef); }; -const updateMemberTokenDiscountPercentage = ( +const updateMemberTokenDiscountPercentage = async ( transaction: ITransaction, + projectId: string, distribution: TokenDistribution | undefined, member: string, stakeValueDiff: number, ) => { + const project = await build5Db().get(COL.PROJECT, projectId); const stakeValue = getStakeForType(distribution, StakeType.DYNAMIC) + stakeValueDiff; - const tier = getTier(stakeValue); + const tier = getTier(project?.config?.tiers || [], stakeValue); if (!tier && stakeValueDiff > 0) { return; } - const discount = tokenTradingFeeDicountPercentage[tier] / 100; + const discount = (project?.config?.tokenTradingFeeDiscountPercentage || [])[tier] / 100; const tokenTradingFeePercentage = getTokenSaleConfig.percentage * (1 - discount); const memberDocRef = build5Db().doc(`${COL.MEMBER}/${member}`); transaction.update(memberDocRef, { tokenTradingFeePercentage }); }; -export const getTier = (stakeValue: number) => { +export const getTier = (tiers: number[], stakeValue: number) => { let tier = 0; while (tiers[tier] <= stakeValue && tier < tiers.length) { ++tier; diff --git a/packages/functions/src/triggers/collection.trigger.ts b/packages/functions/src/triggers/collection.trigger.ts index e2610c742d..ec5b3db0e2 100644 --- a/packages/functions/src/triggers/collection.trigger.ts +++ b/packages/functions/src/triggers/collection.trigger.ts @@ -314,7 +314,8 @@ const setNftForMinting = async (nftId: string, collection: Collection): Promise< collection: nft.collection, }, }; - transaction.create(build5Db().doc(`${COL.TRANSACTION}/${credit.uid}`), credit); + const creditDocRef = build5Db().doc(`${COL.TRANSACTION}/${credit.uid}`); + transaction.create(creditDocRef, credit); } if (nft.locked) { diff --git a/packages/functions/src/triggers/milestone-transactions-triggers/consumed.vote.outputs.ts b/packages/functions/src/triggers/milestone-transactions-triggers/consumed.vote.outputs.ts index 594db4dcab..b91fc716df 100644 --- a/packages/functions/src/triggers/milestone-transactions-triggers/consumed.vote.outputs.ts +++ b/packages/functions/src/triggers/milestone-transactions-triggers/consumed.vote.outputs.ts @@ -1,13 +1,11 @@ -import { ITransaction, build5Db } from '@build-5/database'; +import { build5Db } from '@build-5/database'; import { COL, Proposal, SUB_COL, Transaction, TransactionType } from '@build-5/interfaces'; import dayjs from 'dayjs'; import { getTokenVoteMultiplier } from '../../services/payment/voting-service'; import { serverTime } from '../../utils/dateTime.utils'; -export const processConsumedVoteOutputs = async ( - transaction: ITransaction, - consumedOutputIds: string[], -) => { +export const processConsumedVoteOutputs = async (consumedOutputIds: string[]) => { + const batch = build5Db().batch(); for (const consumedOutput of consumedOutputIds) { const voteTransactionSnap = await build5Db() .collection(COL.TRANSACTION) @@ -27,7 +25,7 @@ export const processConsumedVoteOutputs = async ( const proposalDocRef = build5Db().doc(`${COL.PROPOSAL}/${voteTransaction.payload.proposalId}`); const proposal = await proposalDocRef.get(); if (dayjs().isAfter(proposal.settings.endDate.toDate())) { - transaction.update(voteTransactionDocRef, { + batch.update(voteTransactionDocRef, { 'payload.outputConsumed': true, 'payload.outputConsumedOn': serverTime(), }); @@ -51,9 +49,9 @@ export const processConsumedVoteOutputs = async ( answers: { [value]: build5Db().inc(-prevWeight + currWeight) }, }, }; - transaction.set(proposalDocRef, data, true); + batch.set(proposalDocRef, data, true); - transaction.update(voteTransactionDocRef, { + batch.update(voteTransactionDocRef, { 'payload.weight': currWeight, 'payload.weightMultiplier': currWeightMultiplier, 'payload.outputConsumed': true, @@ -63,7 +61,7 @@ export const processConsumedVoteOutputs = async ( const proposalMemberDocRef = proposalDocRef .collection(SUB_COL.MEMBERS) .doc(voteTransaction.member!); - transaction.set( + batch.set( proposalMemberDocRef, { values: build5Db().arrayRemove({ @@ -75,11 +73,12 @@ export const processConsumedVoteOutputs = async ( }, true, ); - transaction.update(proposalMemberDocRef, { + batch.update(proposalMemberDocRef, { values: build5Db().arrayUnion({ [value]: currWeight, voteTransaction: voteTransaction.uid, }), }); } + await batch.commit(); }; diff --git a/packages/functions/src/triggers/milestone-transactions-triggers/iota-milestone-transaction.trigger.ts b/packages/functions/src/triggers/milestone-transactions-triggers/iota-milestone-transaction.trigger.ts index 9b16d95229..dcd2f7ef4c 100644 --- a/packages/functions/src/triggers/milestone-transactions-triggers/iota-milestone-transaction.trigger.ts +++ b/packages/functions/src/triggers/milestone-transactions-triggers/iota-milestone-transaction.trigger.ts @@ -19,21 +19,17 @@ const handleMilestoneTransactionWrite = return; } try { - return build5Db().runTransaction(async (transaction) => { - const docRef = build5Db().doc(event.data!.after.ref.path); - const milestoneTransaction = await transaction.get>(docRef); - if (!milestoneTransaction || milestoneTransaction.processed) { - return; - } - await confirmTransaction(event.data!.after.ref.path, milestoneTransaction, network); + const docRef = build5Db().doc(event.data!.after.ref.path); + const milestone = await docRef.get(); + if (!milestone || milestone.processed) { + return; + } + await confirmTransaction(event.data!.after.ref.path, milestone, network); - const service = new ProcessingService(transaction); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - await service.processMilestoneTransactions(milestoneTransaction as any); - service.submit(); + const service = new ProcessingService(); + await service.processMilestoneTransactions(milestone); - transaction.update(docRef, { processed: true, processedOn: dayjs().toDate() }); - }); + docRef.update({ processed: true, processedOn: dayjs().toDate() }); } catch (error) { functions.logger.error(`${network} transaction error`, event.data!.after.ref.path, error); } diff --git a/packages/functions/src/triggers/milestone-transactions-triggers/smr-milestone-transaction.trigger.ts b/packages/functions/src/triggers/milestone-transactions-triggers/smr-milestone-transaction.trigger.ts index 3abb3ab8dd..4ddfb51b34 100644 --- a/packages/functions/src/triggers/milestone-transactions-triggers/smr-milestone-transaction.trigger.ts +++ b/packages/functions/src/triggers/milestone-transactions-triggers/smr-milestone-transaction.trigger.ts @@ -22,30 +22,23 @@ const handleMilestoneTransactionWrite = return; } try { - return build5Db().runTransaction(async (transaction) => { - const docRef = build5Db().doc(event.data!.after.ref.path); - const data = await transaction.get>(docRef); - if (!data || data.processed) { - return; - } - await confirmTransaction(event.data!.after.ref.path, data, network); - await updateTokenSupplyData(data); - const adapter = new SmrMilestoneTransactionAdapter(network); - const milestoneTransaction = await adapter.toMilestoneTransaction({ - ...data, - uid: event.params.tranId, - }); - const service = new ProcessingService(transaction); - await service.processMilestoneTransactions(milestoneTransaction); - service.submit(); + const docRef = build5Db().doc(event.data!.after.ref.path); + const data = await docRef.get>(); + if (!data || data.processed) { + return; + } - await processConsumedVoteOutputs( - transaction, - milestoneTransaction.inputs.map((i) => i.outputId!), - ); + await confirmTransaction(event.data!.after.ref.path, data, network); + await updateTokenSupplyData(data); - return transaction.update(docRef, { processed: true, processedOn: dayjs().toDate() }); - }); + const adapter = new SmrMilestoneTransactionAdapter(network); + const milestone = await adapter.toMilestoneTransaction({ ...data, uid: event.params.tranId }); + const service = new ProcessingService(); + await service.processMilestoneTransactions(milestone); + + await processConsumedVoteOutputs(milestone.inputs.map((i) => i.outputId!)); + + return docRef.update({ processed: true, processedOn: dayjs().toDate() }); } catch (error) { functions.logger.error(`${network} transaction error`, event.data!.after.ref.path, error); } diff --git a/packages/functions/src/triggers/token-trading/match-base-token.ts b/packages/functions/src/triggers/token-trading/match-base-token.ts index 6aa3a7f3a2..4e9713f121 100644 --- a/packages/functions/src/triggers/token-trading/match-base-token.ts +++ b/packages/functions/src/triggers/token-trading/match-base-token.ts @@ -21,6 +21,7 @@ import { SmrWallet } from '../../services/wallet/SmrWalletService'; import { WalletService } from '../../services/wallet/wallet'; import { getAddress } from '../../utils/address.utils'; import { packBasicOutput } from '../../utils/basic-output.utils'; +import { getProject } from '../../utils/common.utils'; import { getRoyaltyFees } from '../../utils/royalty.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; import { Match } from './match-token'; @@ -261,7 +262,11 @@ export const matchBaseToken = async ( if (isEmpty(iotaPayments) || isEmpty(smrPayments)) { return { sellerCreditId: undefined, buyerCreditId: undefined, purchase: undefined }; } - [...iotaPayments, ...smrPayments].forEach((payment) => { + iotaPayments.forEach((payment) => { + const docRef = build5Db().doc(`${COL.TRANSACTION}/${payment.uid}`); + transaction.create(docRef, payment); + }); + smrPayments.forEach((payment) => { const docRef = build5Db().doc(`${COL.TRANSACTION}/${payment.uid}`); transaction.create(docRef, payment); }); @@ -287,7 +292,7 @@ export const matchBaseToken = async ( .filter((o) => o.type === TransactionType.BILL_PAYMENT && o.payload.royalty === true) .map((o) => o.uid), - sellerTier: await getMemberTier(seller!), + sellerTier: await getMemberTier(getProject(sell), seller!), sellerTokenTradingFeePercentage: getTokenTradingFee(seller!), }, }; diff --git a/packages/functions/src/triggers/token-trading/match-minted-token.ts b/packages/functions/src/triggers/token-trading/match-minted-token.ts index 71c565e716..c582046c88 100644 --- a/packages/functions/src/triggers/token-trading/match-minted-token.ts +++ b/packages/functions/src/triggers/token-trading/match-minted-token.ts @@ -20,6 +20,7 @@ import { SmrWallet } from '../../services/wallet/SmrWalletService'; import { WalletService } from '../../services/wallet/wallet'; import { getAddress } from '../../utils/address.utils'; import { packBasicOutput } from '../../utils/basic-output.utils'; +import { getProject } from '../../utils/common.utils'; import { getRoyaltyFees } from '../../utils/royalty.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; import { Match } from './match-token'; @@ -327,9 +328,10 @@ export const matchMintedToken = async ( creditToBuyer, ] .filter((t) => t !== undefined) - .forEach((data) => - transaction.create(build5Db().doc(`${COL.TRANSACTION}/${data!.uid}`), data!), - ); + .forEach((data) => { + const docRef = build5Db().doc(`${COL.TRANSACTION}/${data!.uid}`); + transaction.create(docRef, data!); + }); return { purchase: { @@ -345,7 +347,7 @@ export const matchMintedToken = async ( buyerBillPaymentId: billPaymentToSeller.uid, royaltyBillPayments: royaltyBillPayments.map((o) => o.uid), - sellerTier: await getMemberTier(seller), + sellerTier: await getMemberTier(getProject(sell), seller), sellerTokenTradingFeePercentage: getTokenTradingFee(seller), }, sellerCreditId: creditToSeller?.uid, diff --git a/packages/functions/src/triggers/token-trading/match-simple-token.ts b/packages/functions/src/triggers/token-trading/match-simple-token.ts index e63bea1a0b..2f7b957c5a 100644 --- a/packages/functions/src/triggers/token-trading/match-simple-token.ts +++ b/packages/functions/src/triggers/token-trading/match-simple-token.ts @@ -18,6 +18,7 @@ import { import bigDecimal from 'js-big-decimal'; import { isEmpty, tail } from 'lodash'; import { getAddress } from '../../utils/address.utils'; +import { getProject } from '../../utils/common.utils'; import { getRoyaltyFees } from '../../utils/royalty.utils'; import { getRandomEthAddress } from '../../utils/wallet.utils'; import { Match } from './match-token'; @@ -195,9 +196,10 @@ export const matchSimpleToken = async ( if (isEmpty(buyerPayments)) { return { purchase: undefined, buyerCreditId: undefined, sellerCreditId: undefined }; } - buyerPayments.forEach((p) => - transaction.create(build5Db().doc(`${COL.TRANSACTION}/${p.uid}`), p), - ); + buyerPayments.forEach((p) => { + const docRef = build5Db().doc(`${COL.TRANSACTION}/${p.uid}`); + return transaction.create(docRef, p); + }); return { purchase: { @@ -214,7 +216,7 @@ export const matchSimpleToken = async ( .map((p) => p.uid), triggeredBy, - sellerTier: await getMemberTier(seller), + sellerTier: await getMemberTier(getProject(sell), seller), sellerTokenTradingFeePercentage: getTokenTradingFee(seller), }, buyerCreditId: buyerPayments.filter((p) => p.type === TransactionType.CREDIT)[0]?.uid || '', diff --git a/packages/functions/src/triggers/token-trading/match-token.ts b/packages/functions/src/triggers/token-trading/match-token.ts index 39e7b90403..26d2ebd50a 100644 --- a/packages/functions/src/triggers/token-trading/match-token.ts +++ b/packages/functions/src/triggers/token-trading/match-token.ts @@ -129,7 +129,8 @@ const runTradeOrderMatching = async ( postMatchActions(transaction, prevBuy, buy, prevSell, sell); } - transaction.create(build5Db().doc(`${COL.TOKEN_PURCHASE}/${purchase.uid}`), purchase); + const purchaseDocRef = build5Db().doc(`${COL.TOKEN_PURCHASE}/${purchase.uid}`); + transaction.create(purchaseDocRef, purchase); update = isSell ? sell : buy; } transaction.update(build5Db().doc(`${COL.TOKEN_MARKET}/${tradeOrder.uid}`), update); diff --git a/packages/functions/src/triggers/token-trading/token-trade-order.trigger.ts b/packages/functions/src/triggers/token-trading/token-trade-order.trigger.ts index 0798007d2a..7d3d2102a7 100644 --- a/packages/functions/src/triggers/token-trading/token-trade-order.trigger.ts +++ b/packages/functions/src/triggers/token-trading/token-trade-order.trigger.ts @@ -2,6 +2,8 @@ import { build5Db } from '@build-5/database'; import { COL, Member, + Project, + ProjectBilling, StakeType, SUB_COL, TokenDistribution, @@ -16,7 +18,7 @@ import bigDecimal from 'js-big-decimal'; import { scale } from '../../scale.settings'; import { getStakeForType, getTier } from '../../services/stake.service'; import { cancelTradeOrderUtil } from '../../utils/token-trade.utils'; -import { BIG_DECIMAL_PRECISION, getSoonToken } from '../../utils/token.utils'; +import { BIG_DECIMAL_PRECISION } from '../../utils/token.utils'; import { matchTradeOrder } from './match-token'; const runParams = { @@ -63,16 +65,16 @@ const needsHigherBuyAmount = (buy: TokenTradeOrder) => { return price > buy.price; }; -export const getMemberTier = async (member: Member) => { - const soon = await getSoonToken(); - const distributionDocRef = build5Db() - .collection(COL.TOKEN) - .doc(soon.uid) - .collection(SUB_COL.DISTRIBUTION) - .doc(member.uid); +export const getMemberTier = async (projectId: string, member: Member) => { + const project = await build5Db().get(COL.PROJECT, projectId); + if (project?.config?.billing !== ProjectBilling.TOKEN_BASE) { + return 0; + } + const tokenDocRef = build5Db().doc(`${COL.TOKEN}/${project.config.baseTokenUid}`); + const distributionDocRef = tokenDocRef.collection(SUB_COL.DISTRIBUTION).doc(member.uid); const distribution = await distributionDocRef.get(); const stakeValue = getStakeForType(distribution, StakeType.DYNAMIC); - return getTier(stakeValue); + return getTier(project.config.tiers || [], stakeValue); }; export const getTokenTradingFee = (member: Member) => diff --git a/packages/functions/src/utils/common.utils.ts b/packages/functions/src/utils/common.utils.ts index ac5c238c8f..6955583f7f 100644 --- a/packages/functions/src/utils/common.utils.ts +++ b/packages/functions/src/utils/common.utils.ts @@ -1,4 +1,17 @@ -import { Access, Collection, MIN_AMOUNT_TO_TRANSFER, Nft, Restrictions } from '@build-5/interfaces'; +import { build5Db } from '@build-5/database'; +import { + Access, + BaseRecord, + COL, + Collection, + MIN_AMOUNT_TO_TRANSFER, + Nft, + Restrictions, + SOON_PROJECT_ID, + SUB_COL, + WenError, +} from '@build-5/interfaces'; +import { invalidArgument } from './error.utils'; const MAX_RERUNS = 10; @@ -44,3 +57,13 @@ export const getRestrictions = (collection?: Collection, nft?: Nft): Restriction return restrictions; }; + +export const assertIsProjectGuardian = async (project: string, member: string) => { + const projectDocRef = build5Db().doc(`${COL.PROJECT}/${project}`); + const guardianDoc = await projectDocRef.collection(SUB_COL.GUARDIANS).doc(member).get(); + if (!guardianDoc) { + throw invalidArgument(WenError.you_are_not_guardian_of_project); + } +}; + +export const getProject = (data: BaseRecord | undefined) => data?.project || SOON_PROJECT_ID; diff --git a/packages/functions/src/utils/token.utils.ts b/packages/functions/src/utils/token.utils.ts index 7978cf1538..1cf4d302d6 100644 --- a/packages/functions/src/utils/token.utils.ts +++ b/packages/functions/src/utils/token.utils.ts @@ -85,11 +85,6 @@ export const tokenIsInCoolDownPeriod = (token: Token) => dayjs().isAfter(dayjs(token.saleStartDate.toDate()).add(token.saleLength, 'ms')) && dayjs().isBefore(dayjs(token.coolDownEnd.toDate())); -export const getSoonToken = async () => { - const snap = await build5Db().collection(COL.TOKEN).where('symbol', '==', 'SOON').limit(1).get(); - return snap[0]; -}; - export const getTokenForSpace = async (space: string) => { let snap = await build5Db() .collection(COL.TOKEN) diff --git a/packages/functions/src/utils/wallet.utils.ts b/packages/functions/src/utils/wallet.utils.ts index 2e9fe5b4ae..b8b45f9a1e 100644 --- a/packages/functions/src/utils/wallet.utils.ts +++ b/packages/functions/src/utils/wallet.utils.ts @@ -26,7 +26,7 @@ import { Wallet } from 'ethers'; import jwt from 'jsonwebtoken'; import { get } from 'lodash'; import { getCustomTokenLifetime, getJwtSecretKey } from './config.utils'; -import { unAuthenticated } from './error.utils'; +import { invalidArgument, unAuthenticated } from './error.utils'; export const minAddressLength = 42; export const maxAddressLength = 255; @@ -37,24 +37,36 @@ const toHex = (stringToConvert: string) => .map((c) => c.charCodeAt(0).toString(16).padStart(2, '0')) .join(''); -export async function decodeAuth(req: WenRequest, func: WEN_FUNC): Promise { +export async function decodeAuth( + req: WenRequest, + func: WEN_FUNC, + skipProject = false, +): Promise { if (!req) { throw unAuthenticated(WenError.invalid_params); } + let project = ''; + if (!skipProject) { + const decoded = jwt.verify(req.projectApiKey!, getJwtSecretKey()); + project = get(decoded, 'project', ''); + if (!project) { + throw invalidArgument(WenError.invalid_project_api_key); + } + } if (req.signature && req.publicKey) { const address = await validateWithPublicKey(req); - return { address, body: req.body }; + return { address, project, body: req.body }; } if (req.signature) { await validateWithSignature(req); - return { address: req.address, body: req.body }; + return { address: req.address, project, body: req.body }; } if (req.customToken) { await validateWithIdToken(req, func); - return { address: req.address, body: req.body }; + return { address: req.address, project, body: req.body }; } throw unAuthenticated(WenError.signature_or_custom_token_must_be_provided); diff --git a/packages/functions/test/controls/common.ts b/packages/functions/test/controls/common.ts index 31285f8f6e..dc595faf98 100644 --- a/packages/functions/test/controls/common.ts +++ b/packages/functions/test/controls/common.ts @@ -1,8 +1,10 @@ import { build5Db } from '@build-5/database'; import { COL, + DecodedToken, Network, NetworkAddress, + SOON_PROJECT_ID, SUB_COL, Space, TOKEN_SALE_TEST, @@ -23,8 +25,15 @@ import * as ipUtils from '../../src/utils/ip.utils'; import * as wallet from '../../src/utils/wallet.utils'; import { MEDIA, getWallet, testEnv } from '../set-up'; -export const mockWalletReturnValue = (walletSpy: any, address: NetworkAddress, body: T) => - walletSpy.mockReturnValue(Promise.resolve({ address, body })); +export const mockWalletReturnValue = ( + walletSpy: any, + address: NetworkAddress, + body: T, + noProject = false, +) => { + const decodedToken: DecodedToken = { address, body, project: noProject ? '' : SOON_PROJECT_ID }; + return walletSpy.mockReturnValue(Promise.resolve(decodedToken)); +}; export const expectThrow = async (call: C | Promise, error: E, message?: string) => { try { diff --git a/packages/functions/test/controls/project/project.create.spec.ts b/packages/functions/test/controls/project/project.create.spec.ts new file mode 100644 index 0000000000..cb889c0dce --- /dev/null +++ b/packages/functions/test/controls/project/project.create.spec.ts @@ -0,0 +1,107 @@ +import { build5Db } from '@build-5/database'; +import { + COL, + Project, + ProjectBilling, + ProjectGuardian, + SUB_COL, + Token, + TokenStatus, + WenError, +} from '@build-5/interfaces'; +import { createProject } from '../../../src/runtime/firebase/project/index'; +import * as wallet from '../../../src/utils/wallet.utils'; +import { testEnv } from '../../set-up'; +import { createMember, expectThrow, getRandomSymbol, mockWalletReturnValue } from '../common'; + +describe('Project create', () => { + let walletSpy: any; + let guardian: string; + let token: Token; + + beforeAll(async () => { + walletSpy = jest.spyOn(wallet, 'decodeAuth'); + }); + + beforeEach(async () => { + guardian = await createMember(walletSpy); + const tokenId = wallet.getRandomEthAddress(); + token = { + uid: tokenId, + symbol: getRandomSymbol(), + name: 'MyToken', + space: 'myspace', + status: TokenStatus.AVAILABLE, + approved: true, + }; + await build5Db().doc(`${COL.TOKEN}/${tokenId}`).set(token); + }); + + it('Should create volume based project', async () => { + const dummyProject = { + name: 'My project', + contactEmail: 'myemail@gmail.com', + config: { billing: ProjectBilling.VOLUME_BASED }, + }; + mockWalletReturnValue(walletSpy, guardian, dummyProject); + const { project: newProject } = await testEnv.wrap(createProject)({}); + + const projectDocRef = build5Db().doc(`${COL.PROJECT}/${newProject.uid}`); + const project = await projectDocRef.get(); + expect(project?.name).toBe(dummyProject.name); + expect(project?.contactEmail).toBe(dummyProject.contactEmail); + expect(project?.deactivated).toBe(false); + expect(project?.config?.billing).toBe(ProjectBilling.VOLUME_BASED); + + const projectGuardianDocRef = projectDocRef.collection(SUB_COL.GUARDIANS).doc(guardian); + const projectGuardian = await projectGuardianDocRef.get(); + expect(projectGuardian?.uid).toBe(guardian); + expect(projectGuardian?.parentCol).toBe(COL.PROJECT); + expect(projectGuardian?.parentId).toBe(project?.uid); + }); + + it('Should throw, volume based project with token based data', async () => { + const dummyProject = { + name: 'My project', + contactEmail: 'myemail@gmail.com', + config: { billing: ProjectBilling.VOLUME_BASED, tiers: [1, 2, 3, 4] }, + }; + mockWalletReturnValue(walletSpy, guardian, dummyProject); + await expectThrow(testEnv.wrap(createProject)({}), WenError.invalid_params.key); + }); + + it('Should create token based project', async () => { + const dummyProject = { + name: 'My project', + contactEmail: 'myemail@gmail.com', + config: { + billing: ProjectBilling.TOKEN_BASE, + tiers: [1, 2, 3, 4, 5], + tokenTradingFeeDiscountPercentage: [0, 0, 0, 0, 0], + baseTokenSymbol: token.symbol, + baseTokenUid: token.uid, + }, + }; + mockWalletReturnValue(walletSpy, guardian, dummyProject); + const { project: newProject } = await testEnv.wrap(createProject)({}); + + const projectDocRef = build5Db().doc(`${COL.PROJECT}/${newProject.uid}`); + const project = await projectDocRef.get(); + expect(project?.name).toBe(dummyProject.name); + expect(project?.contactEmail).toBe(dummyProject.contactEmail); + expect(project?.deactivated).toBe(false); + expect(project?.config?.billing).toBe(ProjectBilling.TOKEN_BASE); + expect(project?.config?.tiers).toEqual(dummyProject.config.tiers); + expect(project?.config?.tokenTradingFeeDiscountPercentage).toEqual( + dummyProject.config.tokenTradingFeeDiscountPercentage, + ); + expect(project?.config?.baseTokenSymbol).toBe(dummyProject.config.baseTokenSymbol); + expect(project?.config?.baseTokenUid).toBe(dummyProject.config.baseTokenUid); + + const projectGuardianDocRef = projectDocRef.collection(SUB_COL.GUARDIANS).doc(guardian); + const projectGuardian = await projectGuardianDocRef.get(); + expect(projectGuardian?.uid).toBe(guardian); + expect(projectGuardian?.parentCol).toBe(COL.PROJECT); + expect(projectGuardian?.parentId).toBe(project?.uid); + }); +}); diff --git a/packages/functions/test/controls/project/project.deactivate.spec.ts b/packages/functions/test/controls/project/project.deactivate.spec.ts new file mode 100644 index 0000000000..cfdf3c091b --- /dev/null +++ b/packages/functions/test/controls/project/project.deactivate.spec.ts @@ -0,0 +1,40 @@ +import { build5Db } from '@build-5/database'; +import { COL, Project, WenError } from '@build-5/interfaces'; +import { createProject, deactivateProject } from '../../../src/runtime/firebase/project/index'; +import * as wallet from '../../../src/utils/wallet.utils'; +import { testEnv } from '../../set-up'; +import { createMember, expectThrow, mockWalletReturnValue } from '../common'; + +describe('Project create', () => { + let walletSpy: any; + let guardian: string; + let project: string; + + beforeEach(async () => { + walletSpy = jest.spyOn(wallet, 'decodeAuth'); + guardian = await createMember(walletSpy); + + const dummyProject = { name: 'My project', contactEmail: 'myemail@gmail.com' }; + mockWalletReturnValue(walletSpy, guardian, dummyProject); + const { uid } = await testEnv.wrap(createProject)({}); + project = uid; + }); + + it('Should deactivate project', async () => { + mockWalletReturnValue(walletSpy, guardian, { project }); + await testEnv.wrap(deactivateProject)({}); + + const projectDocRef = build5Db().doc(`${COL.PROJECT}/${project}`); + const projectData = await projectDocRef.get(); + expect(projectData?.deactivated).toBe(true); + }); + + it('Should throw, not guardian of the project', async () => { + const member = await createMember(walletSpy); + mockWalletReturnValue(walletSpy, member, { project }); + await expectThrow( + testEnv.wrap(deactivateProject)({}), + WenError.you_are_not_guardian_of_project.key, + ); + }); +}); diff --git a/packages/interfaces/src/api/post/CreateMemberRequest.ts b/packages/interfaces/src/api/post/CreateMemberRequest.ts index 30962a4278..0ff0e35b27 100644 --- a/packages/interfaces/src/api/post/CreateMemberRequest.ts +++ b/packages/interfaces/src/api/post/CreateMemberRequest.ts @@ -3,8 +3,6 @@ * Do not modify this file manually */ -import { NetworkAddress } from '../../models'; - /** * Request object to create a member */ @@ -12,5 +10,5 @@ export interface CreateMemberRequest { /** * Wallet address of the member */ - address: NetworkAddress; + address: string; } diff --git a/packages/interfaces/src/api/post/ProjectCreate.ts b/packages/interfaces/src/api/post/ProjectCreate.ts new file mode 100644 index 0000000000..3eb6bb8232 --- /dev/null +++ b/packages/interfaces/src/api/post/ProjectCreate.ts @@ -0,0 +1,43 @@ +/** + * This file was automatically generated by joi-to-typescript + * Do not modify this file manually + */ + +/** + * Request object to create a project. + */ +export interface ProjectCreateRequest { + /** + * Config for this project. + */ + config: { + /** + * Base token symbol for this project. Set only if billing type is token_based + */ + baseTokenSymbol?: string; + /** + * Base token uid for this project. Set only if billing type is token_based + */ + baseTokenUid?: string; + /** + * Billing type of the project. + */ + billing: 'token_based' | 'volume_based'; + /** + * Tiers for this project. Set only if billing type is token_based + */ + tiers?: any[]; + /** + * Discounts for this project. Set only if billing type is token_based + */ + tokenTradingFeeDiscountPercentage?: any[]; + }; + /** + * Email address of a contact for the project. + */ + contactEmail?: string; + /** + * Name of the project. Minimum 3, maximum 40 character + */ + name: string; +} diff --git a/packages/interfaces/src/api/post/ProjectDeactivate.ts b/packages/interfaces/src/api/post/ProjectDeactivate.ts new file mode 100644 index 0000000000..178dba1b34 --- /dev/null +++ b/packages/interfaces/src/api/post/ProjectDeactivate.ts @@ -0,0 +1,11 @@ +/** + * This file was automatically generated by joi-to-typescript + * Do not modify this file manually + */ + +/** + * Request object to deactivate a project. + */ +export interface ProjectDeactivateRequest { + project: string; +} diff --git a/packages/interfaces/src/api/post/index.ts b/packages/interfaces/src/api/post/index.ts index bd3a58584a..403efe8456 100644 --- a/packages/interfaces/src/api/post/index.ts +++ b/packages/interfaces/src/api/post/index.ts @@ -29,6 +29,8 @@ export * from './NftSetForSaleRequest'; export * from './NftStakeRequest'; export * from './NftUpdateUnsoldRequest'; export * from './NftWithdrawRequest'; +export * from './ProjectCreate'; +export * from './ProjectDeactivate'; export * from './ProposalApproveRequest'; export * from './ProposalCreateRequest'; export * from './ProposalRejectRequest'; diff --git a/packages/interfaces/src/config.ts b/packages/interfaces/src/config.ts index f5cd2be99f..c02aaacf85 100644 --- a/packages/interfaces/src/config.ts +++ b/packages/interfaces/src/config.ts @@ -147,9 +147,6 @@ export const MAX_FIELD_VALUE_LENGTH = 100; export const MIN_WEEKS_TO_STAKE = 1; export const MAX_WEEKS_TO_STAKE = 52; -export const tiers = [0, 10, 4000, 6000, 15000].map((v) => v * MIN_IOTA_AMOUNT); -export const tokenTradingFeeDicountPercentage = [0, 25, 50, 75, 100]; - export const RANKING = { MIN_RANK: -100, MAX_RANK: 100, @@ -205,3 +202,5 @@ export const API_TIMEOUT_SECONDS = 600; export const API_RETRY_TIMEOUT = 2500; export const EXTEND_AUCTION_WITHIN = 5 * 60000; + +export const SOON_PROJECT_ID = '0x46223edd4157635dfc6399155609f301decbfd88'; diff --git a/packages/interfaces/src/errors.ts b/packages/interfaces/src/errors.ts index 3470acaef6..8471575e6e 100644 --- a/packages/interfaces/src/errors.ts +++ b/packages/interfaces/src/errors.ts @@ -305,4 +305,6 @@ export const WenError = { code: 2138, key: 'Invalid collection id.', }, + you_are_not_guardian_of_project: { code: 2139, key: 'You are not a guardian of the project.' }, + invalid_project_api_key: { code: 2140, key: 'Invalid project api keys.' }, }; diff --git a/packages/interfaces/src/functions/index.ts b/packages/interfaces/src/functions/index.ts index 359808d102..ddbc548089 100644 --- a/packages/interfaces/src/functions/index.ts +++ b/packages/interfaces/src/functions/index.ts @@ -101,6 +101,10 @@ export enum WEN_FUNC { api = 'api', uploadFile = 'uploadfile', + + createProject = 'createProject', + deactivateProject = 'deactivateProject', + createProjetApiKey = 'createProjetApiKey', } export interface cMemberNotExists { @@ -109,6 +113,7 @@ export interface cMemberNotExists { export interface DecodedToken { address: NetworkAddress; + project: string; body: any; } diff --git a/packages/interfaces/src/models/base.ts b/packages/interfaces/src/models/base.ts index b1e72a80d0..03effaf30a 100644 --- a/packages/interfaces/src/models/base.ts +++ b/packages/interfaces/src/models/base.ts @@ -32,6 +32,7 @@ export class Timestamp { export interface WenRequest { address: NetworkAddress; signature?: string; + projectApiKey?: string; customToken?: string; publicKey?: { hex: string; @@ -63,6 +64,7 @@ export enum COL { STAKE_REWARD = 'stake_reward', NFT_STAKE = 'nft_stake', AIRDROP = 'airdrop', + PROJECT = 'project', MNEMONIC = '_mnemonic', SYSTEM = '_system', @@ -81,6 +83,7 @@ export const enum SUB_COL { STATS = 'stats', VOTES = 'votes', RANKS = 'ranks', + _API_KEY = '_api_key', } export const enum AWARD_COL { @@ -109,6 +112,14 @@ export interface BaseSubCollection { * Every object will have these basic fields. */ export interface BaseRecord extends Base { + /** + * The project to which this record belongs to. + */ + project?: string; + /** + * The project to which this record belongs to. + */ + projects?: { [key: string]: boolean }; /** * Date/time it was created on. */ diff --git a/packages/interfaces/src/models/index.ts b/packages/interfaces/src/models/index.ts index 8f657dc851..00e21ab588 100644 --- a/packages/interfaces/src/models/index.ts +++ b/packages/interfaces/src/models/index.ts @@ -8,6 +8,7 @@ export * from './mnemonic'; export * from './nft'; export * from './nftStake'; export * from './notification'; +export * from './project'; export * from './proposal'; export * from './space'; export * from './stake'; diff --git a/packages/interfaces/src/models/project.ts b/packages/interfaces/src/models/project.ts new file mode 100644 index 0000000000..84c56e9682 --- /dev/null +++ b/packages/interfaces/src/models/project.ts @@ -0,0 +1,56 @@ +import { BaseRecord, BaseSubCollection, Timestamp } from './base'; + +export interface Project extends BaseRecord { + /** + * Name of the project + */ + name: string; + /** + * Email of a contact to this project + */ + contactEmail?: string; + /** + * Boolean flag to show if project is deactivated, default value is undefined + */ + deactivated?: boolean; + /** + * The config object of the project + */ + config: ProjectConfig; +} + +export enum ProjectBilling { + TOKEN_BASE = 'token_based', + VOLUME_BASED = 'volume_based', +} + +export interface ProjectConfig { + /** + *The billing type of the project + */ + billing: ProjectBilling; + + tiers?: number[]; + tokenTradingFeeDiscountPercentage?: number[]; + baseTokenSymbol?: string; + baseTokenUid?: string; +} + +/** + * Project Guardian subcollection. + */ +export interface ProjectGuardian extends BaseSubCollection { + /** + * Member ID {@link Member} + */ + uid: string; + /** + * Guardian joined on + */ + createdOn: Timestamp; +} + +export interface ProjectApiKey extends BaseSubCollection { + uid: string; + createdOn: Timestamp; +}