Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Provision mainnet account for customers on account bootstrap [DEV-4203] #567

Merged
merged 9 commits into from
Aug 6, 2024
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -475,3 +475,5 @@ cython_debug/
#.idea/

*.tar
*.prod
*.local
136 changes: 96 additions & 40 deletions src/controllers/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { SafeAPIResponse } from '../../types/common.js';
import { RoleEntity } from '../../database/entities/role.entity.js';
import { getStripeObjectKey } from '../../utils/index.js';
import type { SupportedPlanTypes } from '../../types/admin.js';
import { KeyService } from '../../services/api/key.js';
dotenv.config();

export class AccountController {
Expand Down Expand Up @@ -166,8 +167,6 @@ export class AccountController {
// 9. Check the token balance for Testnet account
// 10. Add the Stripe account to the Customer

let paymentAccount: PaymentAccountEntity | null;

// 1. Get logTo UserId from request body
if (!request.body.user || !request.body.user.id || !request.body.user.primaryEmail) {
return response.status(StatusCodes.BAD_REQUEST).json({
Expand Down Expand Up @@ -288,54 +287,48 @@ export class AccountController {

// 6. Check is paymentAccount exists for the customer
const accounts = await PaymentAccountService.instance.find({ customer: customerEntity });
if (accounts.length === 0) {
const key = await new IdentityServiceStrategySetup(customerEntity.customerId).agent.createKey(
SupportedKeyTypes.Secp256k1,
customerEntity
);
if (!key) {
return response.status(StatusCodes.BAD_REQUEST).json({
error: 'PaymentAccount is not found in database: Key was not created',
} satisfies UnsuccessfulResponseBody);
}
paymentAccount = (await PaymentAccountService.instance.create(
CheqdNetwork.Testnet,
true,
customerEntity,
key
)) as PaymentAccountEntity;
if (!paymentAccount) {
return response.status(StatusCodes.BAD_REQUEST).json({
error: 'PaymentAccount is not found in database: Payment account was not created',
} satisfies UnsuccessfulResponseBody);
}
// Notify
await eventTracker.notify({
message: EventTracker.compileBasicNotification(
'PaymentAccount was not found in database: Payment account with address: ' +
paymentAccount.address +
' was created'
),
severity: 'info',
});
} else {
paymentAccount = accounts[0];

const mainnetAccountResponse = await AccountController.provisionCustomerAccount(
CheqdNetwork.Mainnet,
accounts,
customerEntity
);
if (!mainnetAccountResponse.success) {
return response.status(mainnetAccountResponse.status).json({
error: mainnetAccountResponse.error,
} satisfies UnsuccessfulResponseBody);
}

const testnetAccountResponse = await AccountController.provisionCustomerAccount(
CheqdNetwork.Testnet,
accounts,
customerEntity
);
if (!testnetAccountResponse.success) {
return response.status(testnetAccountResponse.status).json({
error: testnetAccountResponse.error,
} satisfies UnsuccessfulResponseBody);
}

const mainnetAccount = mainnetAccountResponse.data;
const testnetAccount = testnetAccountResponse.data;

// 7. Assign default role on LogTo
const customDataFromLogTo = await logToHelper.getCustomData(userEntity.logToId);

// 8. Create custom_data and update the userInfo (send it to the LogTo)
if (Object.keys(customDataFromLogTo.data).length === 0 && paymentAccount.address) {
if (Object.keys(customDataFromLogTo.data).length === 0 && testnetAccount.address && mainnetAccount.address) {
const customData = {
customer: {
id: customerEntity.customerId,
name: customerEntity.name,
},
paymentAccount: {
address: paymentAccount.address,
testnet: testnetAccount.address,
mainnet: mainnetAccount.address,
},
};

const _r = await logToHelper.updateCustomData(userEntity.logToId, customData);
if (_r.status !== 200) {
return response.status(_r.status).json({
Expand All @@ -345,12 +338,12 @@ export class AccountController {
}

// 9. Check the token balance for Testnet account
if (paymentAccount.address && process.env.ENABLE_ACCOUNT_TOPUP === 'true') {
const balances = await checkBalance(paymentAccount.address, process.env.TESTNET_RPC_URL);
if (testnetAccount.address && process.env.ENABLE_ACCOUNT_TOPUP === 'true') {
const balances = await checkBalance(testnetAccount.address, process.env.TESTNET_RPC_URL);
const balance = balances[0];
if (!balance || +balance.amount < TESTNET_MINIMUM_BALANCE * Math.pow(10, DEFAULT_DENOM_EXPONENT)) {
// 3.1 If it's less then required for DID creation - assign new portion from testnet-faucet
const resp = await FaucetHelper.delegateTokens(paymentAccount.address);
const resp = await FaucetHelper.delegateTokens(testnetAccount.address);
if (resp.status !== StatusCodes.OK) {
return response.status(StatusCodes.BAD_GATEWAY).json({
error: resp.error,
Expand All @@ -359,7 +352,7 @@ export class AccountController {
// Notify
await eventTracker.notify({
message: EventTracker.compileBasicNotification(
`Testnet account with address: ${paymentAccount.address} was funded with ${TESTNET_MINIMUM_BALANCE}`
`Testnet account with address: ${testnetAccount.address} was funded with ${TESTNET_MINIMUM_BALANCE}`
),
severity: 'info',
});
Expand All @@ -381,6 +374,69 @@ export class AccountController {
return response.status(StatusCodes.OK).json({});
}

static async provisionCustomerAccount(
network: CheqdNetwork,
accounts: PaymentAccountEntity[],
customerEntity: CustomerEntity
): Promise<SafeAPIResponse<PaymentAccountEntity>> {
const existingAccount = accounts.find((acc) => acc.namespace === network);
if (existingAccount) {
const key = await KeyService.instance.get(existingAccount.key.kid);
if (key) {
return {
success: true,
status: 200,
data: existingAccount,
};
}

return {
success: false,
status: 412, // precondition
error: `Error: account key not found for kid: ${existingAccount.key.kid}`,
};
}

const key = await new IdentityServiceStrategySetup(customerEntity.customerId).agent.createKey(
SupportedKeyTypes.Secp256k1,
customerEntity
);
if (!key) {
return {
success: false,
status: 400,
error: `PaymentAccount is not found in database: ${network} key was not created`,
};
}

const account = (await PaymentAccountService.instance.create(
network,
true,
customerEntity,
key
)) as PaymentAccountEntity;
if (!account) {
return {
success: false,
status: 400,
error: 'PaymentAccount is not found in database: Payment account was not created',
};
}

await eventTracker.notify({
message: EventTracker.compileBasicNotification(
`PaymentAccount was not found in database: Payment account with address: ${account.address} on ${network} was created`
),
severity: 'info',
});

return {
success: true,
status: 200,
data: account,
};
}

/**
* @openapi
*
Expand Down
Loading