forked from cerc-io/snowballtools-base
Pass payment tx hash in deployment request (#19)
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75) Co-authored-by: IshaVenikar <ishavenikar7@gmail.com> Reviewed-on: cerc-io/snowballtools-base#19
This commit is contained in:
parent
519e318190
commit
0f18bc978e
@ -18,6 +18,9 @@ export class Deployer {
|
|||||||
@Column('varchar', { nullable: true })
|
@Column('varchar', { nullable: true })
|
||||||
minimumPayment!: string | null;
|
minimumPayment!: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', { nullable: true })
|
||||||
|
paymentAddress!: string | null;
|
||||||
|
|
||||||
@ManyToMany(() => Project, (project) => project.deployers)
|
@ManyToMany(() => Project, (project) => project.deployers)
|
||||||
projects!: Project[];
|
projects!: Project[];
|
||||||
}
|
}
|
||||||
|
@ -38,19 +38,17 @@ export interface ApplicationDeploymentRequest {
|
|||||||
auction?: string;
|
auction?: string;
|
||||||
config: string;
|
config: string;
|
||||||
meta: string;
|
meta: string;
|
||||||
|
payment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplicationDeploymentRemovalRequest {
|
export interface ApplicationDeploymentRemovalRequest {
|
||||||
type: string;
|
type: string;
|
||||||
version: string;
|
version: string;
|
||||||
deployment: string;
|
deployment: string;
|
||||||
|
auction?: string;
|
||||||
|
payment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplicationDeploymentRemovalRequest {
|
|
||||||
type: string;
|
|
||||||
version: string;
|
|
||||||
deployment: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApplicationRecord {
|
export interface ApplicationRecord {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -52,6 +52,10 @@ export class Project {
|
|||||||
@Column('varchar', { nullable: true })
|
@Column('varchar', { nullable: true })
|
||||||
auctionId!: string | null;
|
auctionId!: string | null;
|
||||||
|
|
||||||
|
// Tx hash for sending coins from snowball to deployer
|
||||||
|
@Column('varchar', { nullable: true })
|
||||||
|
txHash!: string | null;
|
||||||
|
|
||||||
@ManyToMany(() => Deployer, (deployer) => (deployer.projects))
|
@ManyToMany(() => Deployer, (deployer) => (deployer.projects))
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
deployers!: Deployer[]
|
deployers!: Deployer[]
|
||||||
@ -66,12 +70,10 @@ export class Project {
|
|||||||
@Column('varchar', { nullable: true })
|
@Column('varchar', { nullable: true })
|
||||||
framework!: string | null;
|
framework!: string | null;
|
||||||
|
|
||||||
|
// Address of the user who created the project i.e. requested deployments
|
||||||
@Column('varchar')
|
@Column('varchar')
|
||||||
paymentAddress!: string;
|
paymentAddress!: string;
|
||||||
|
|
||||||
@Column('varchar')
|
|
||||||
txHash!: string;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'simple-array'
|
type: 'simple-array'
|
||||||
})
|
})
|
||||||
|
@ -5,8 +5,8 @@ import { Octokit } from 'octokit';
|
|||||||
import { inc as semverInc } from 'semver';
|
import { inc as semverInc } from 'semver';
|
||||||
import { DeepPartial } from 'typeorm';
|
import { DeepPartial } from 'typeorm';
|
||||||
|
|
||||||
import { Account, Registry as LaconicRegistry, getGasPrice, parseGasAndFees } from '@cerc-io/registry-sdk';
|
import { Account, DEFAULT_GAS_ESTIMATION_MULTIPLIER, Registry as LaconicRegistry, getGasPrice, parseGasAndFees } from '@cerc-io/registry-sdk';
|
||||||
import { IndexedTx } from '@cosmjs/stargate';
|
import { DeliverTxResponse, IndexedTx } from '@cosmjs/stargate';
|
||||||
|
|
||||||
import { RegistryConfig } from './config';
|
import { RegistryConfig } from './config';
|
||||||
import {
|
import {
|
||||||
@ -244,10 +244,11 @@ export class Registry {
|
|||||||
deployment: Deployment,
|
deployment: Deployment,
|
||||||
appName: string,
|
appName: string,
|
||||||
repository: string,
|
repository: string,
|
||||||
auctionId?: string,
|
auctionId?: string | null,
|
||||||
lrn: string,
|
lrn: string,
|
||||||
environmentVariables: { [key: string]: string },
|
environmentVariables: { [key: string]: string },
|
||||||
dns: string,
|
dns: string,
|
||||||
|
payment?: string | null
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
applicationDeploymentRequestId: string;
|
applicationDeploymentRequestId: string;
|
||||||
applicationDeploymentRequestData: ApplicationDeploymentRequest;
|
applicationDeploymentRequestData: ApplicationDeploymentRequest;
|
||||||
@ -281,6 +282,7 @@ export class Registry {
|
|||||||
}),
|
}),
|
||||||
deployer: data.lrn,
|
deployer: data.lrn,
|
||||||
...(data.auctionId && { auction: data.auctionId }),
|
...(data.auctionId && { auction: data.auctionId }),
|
||||||
|
...(data.payment && { payment: data.payment }),
|
||||||
};
|
};
|
||||||
|
|
||||||
await sleep(SLEEP_DURATION);
|
await sleep(SLEEP_DURATION);
|
||||||
@ -322,7 +324,11 @@ export class Registry {
|
|||||||
paymentAddress: auctionWinner,
|
paymentAddress: auctionWinner,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const record of records) {
|
const newRecords = records.filter(record => {
|
||||||
|
return record.names !== null && record.names.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const record of newRecords) {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
deployerRecords.push(record);
|
deployerRecords.push(record);
|
||||||
break;
|
break;
|
||||||
@ -428,6 +434,8 @@ export class Registry {
|
|||||||
async createApplicationDeploymentRemovalRequest(data: {
|
async createApplicationDeploymentRemovalRequest(data: {
|
||||||
deploymentId: string;
|
deploymentId: string;
|
||||||
deployerLrn: string;
|
deployerLrn: string;
|
||||||
|
auctionId?: string | null;
|
||||||
|
payment?: string | null;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
applicationDeploymentRemovalRequestId: string;
|
applicationDeploymentRemovalRequestId: string;
|
||||||
applicationDeploymentRemovalRequestData: ApplicationDeploymentRemovalRequest;
|
applicationDeploymentRemovalRequestData: ApplicationDeploymentRemovalRequest;
|
||||||
@ -436,7 +444,9 @@ export class Registry {
|
|||||||
type: APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE,
|
type: APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE,
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
deployment: data.deploymentId,
|
deployment: data.deploymentId,
|
||||||
deployer: data.deployerLrn
|
deployer: data.deployerLrn,
|
||||||
|
...(data.auctionId && { auction: data.auctionId }),
|
||||||
|
...(data.payment && { payment: data.payment }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
@ -484,19 +494,23 @@ export class Registry {
|
|||||||
return this.registry.getAuctionsByIds([auctionId]);
|
return this.registry.getAuctionsByIds([auctionId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendTokensToAccount(receiverAddress: string, amount: string): Promise<any> {
|
async sendTokensToAccount(receiverAddress: string, amount: string): Promise<DeliverTxResponse> {
|
||||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
await registryTransactionWithRetry(() =>
|
const account = await this.getAccount();
|
||||||
this.registry.sendCoins(
|
const laconicClient = await this.registry.getLaconicClient(account);
|
||||||
{
|
const txResponse: DeliverTxResponse =
|
||||||
amount,
|
await registryTransactionWithRetry(() =>
|
||||||
denom: 'alnt',
|
laconicClient.sendTokens(account.address, receiverAddress,
|
||||||
destinationAddress: receiverAddress
|
[
|
||||||
},
|
{
|
||||||
this.registryConfig.privateKey,
|
denom: 'alnt',
|
||||||
fee
|
amount
|
||||||
)
|
}
|
||||||
);
|
],
|
||||||
|
fee || DEFAULT_GAS_ESTIMATION_MULTIPLIER)
|
||||||
|
);
|
||||||
|
|
||||||
|
return txResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccount(): Promise<Account> {
|
async getAccount(): Promise<Account> {
|
||||||
|
@ -140,6 +140,7 @@ type Deployer {
|
|||||||
deployerId: String!
|
deployerId: String!
|
||||||
deployerApiUrl: String!
|
deployerApiUrl: String!
|
||||||
minimumPayment: String
|
minimumPayment: String
|
||||||
|
paymentAddress: String
|
||||||
createdAt: String!
|
createdAt: String!
|
||||||
updatedAt: String!
|
updatedAt: String!
|
||||||
}
|
}
|
||||||
|
@ -648,7 +648,9 @@ export class Service {
|
|||||||
repository: repoUrl,
|
repository: repoUrl,
|
||||||
environmentVariables: environmentVariablesObj,
|
environmentVariables: environmentVariablesObj,
|
||||||
dns: `${newDeployment.project.name}`,
|
dns: `${newDeployment.project.name}`,
|
||||||
lrn: deployer!.deployerLrn!
|
lrn: deployer!.deployerLrn!,
|
||||||
|
payment: data.project.txHash,
|
||||||
|
auctionId: data.project.auctionId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,6 +662,8 @@ export class Service {
|
|||||||
lrn: deployer!.deployerLrn!,
|
lrn: deployer!.deployerLrn!,
|
||||||
environmentVariables: environmentVariablesObj,
|
environmentVariables: environmentVariablesObj,
|
||||||
dns: `${newDeployment.project.name}-${newDeployment.id}`,
|
dns: `${newDeployment.project.name}-${newDeployment.id}`,
|
||||||
|
payment: data.project.txHash,
|
||||||
|
auctionId: data.project.auctionId
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.db.updateDeploymentById(newDeployment.id, {
|
await this.db.updateDeploymentById(newDeployment.id, {
|
||||||
@ -886,21 +890,53 @@ export class Service {
|
|||||||
per_page: 1,
|
per_page: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create deployment with prod branch and latest commit
|
|
||||||
const deploymentData = {
|
|
||||||
project,
|
|
||||||
branch: project.prodBranch,
|
|
||||||
environment: Environment.Production,
|
|
||||||
domain: null,
|
|
||||||
commitHash: latestCommit.sha,
|
|
||||||
commitMessage: latestCommit.commit.message,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (auctionParams) {
|
if (auctionParams) {
|
||||||
|
// Create deployment with prod branch and latest commit
|
||||||
|
const deploymentData = {
|
||||||
|
project,
|
||||||
|
branch: project.prodBranch,
|
||||||
|
environment: Environment.Production,
|
||||||
|
domain: null,
|
||||||
|
commitHash: latestCommit.sha,
|
||||||
|
commitMessage: latestCommit.commit.message,
|
||||||
|
};
|
||||||
const { applicationDeploymentAuctionId } = await this.laconicRegistry.createApplicationDeploymentAuction(repo, octokit, auctionParams!, deploymentData);
|
const { applicationDeploymentAuctionId } = await this.laconicRegistry.createApplicationDeploymentAuction(repo, octokit, auctionParams!, deploymentData);
|
||||||
await this.updateProject(project.id, { auctionId: applicationDeploymentAuctionId });
|
await this.updateProject(project.id, { auctionId: applicationDeploymentAuctionId });
|
||||||
} else {
|
} else {
|
||||||
const newDeployment = await this.createDeployment(user.id, octokit, deploymentData, lrn);
|
const deployer = await this.db.getDeployerByLRN(lrn!);
|
||||||
|
|
||||||
|
if (!deployer) {
|
||||||
|
log('Invalid deployer LRN');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployer.minimumPayment && project.txHash) {
|
||||||
|
const amountToBePaid = deployer?.minimumPayment.replace(/\D/g, '').toString();
|
||||||
|
|
||||||
|
const txResponse = await this.laconicRegistry.sendTokensToAccount(
|
||||||
|
deployer?.paymentAddress!,
|
||||||
|
amountToBePaid
|
||||||
|
);
|
||||||
|
|
||||||
|
const txHash = txResponse.transactionHash;
|
||||||
|
if (txHash) {
|
||||||
|
await this.updateProject(project.id, { txHash });
|
||||||
|
project.txHash = txHash;
|
||||||
|
log('Funds transferrend to deployer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deploymentData = {
|
||||||
|
project,
|
||||||
|
branch: project.prodBranch,
|
||||||
|
environment: Environment.Production,
|
||||||
|
domain: null,
|
||||||
|
commitHash: latestCommit.sha,
|
||||||
|
commitMessage: latestCommit.commit.message,
|
||||||
|
deployer
|
||||||
|
};
|
||||||
|
|
||||||
|
const newDeployment = await this.createDeployment(user.id, octokit, deploymentData);
|
||||||
// Update project with deployer
|
// Update project with deployer
|
||||||
await this.updateProjectWithDeployer(newDeployment.projectId, newDeployment.deployer);
|
await this.updateProjectWithDeployer(newDeployment.projectId, newDeployment.deployer);
|
||||||
}
|
}
|
||||||
@ -1141,14 +1177,18 @@ export class Service {
|
|||||||
|
|
||||||
await this.laconicRegistry.createApplicationDeploymentRemovalRequest({
|
await this.laconicRegistry.createApplicationDeploymentRemovalRequest({
|
||||||
deploymentId: latestRecord.id,
|
deploymentId: latestRecord.id,
|
||||||
deployerLrn: deployment.deployer.deployerLrn
|
deployerLrn: deployment.deployer.deployerLrn,
|
||||||
|
auctionId: deployment.project.auctionId,
|
||||||
|
payment: deployment.project.txHash
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const result =
|
const result =
|
||||||
await this.laconicRegistry.createApplicationDeploymentRemovalRequest({
|
await this.laconicRegistry.createApplicationDeploymentRemovalRequest({
|
||||||
deploymentId: deployment.applicationDeploymentRecordId,
|
deploymentId: deployment.applicationDeploymentRecordId,
|
||||||
deployerLrn: deployment.deployer.deployerLrn
|
deployerLrn: deployment.deployer.deployerLrn,
|
||||||
|
auctionId: deployment.project.auctionId,
|
||||||
|
payment: deployment.project.txHash
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.db.updateDeploymentById(deployment.id, {
|
await this.db.updateDeploymentById(deployment.id, {
|
||||||
@ -1342,18 +1382,21 @@ export class Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auction = await this.getAuctionData(project.auctionId);
|
const auction = await this.getAuctionData(project.auctionId);
|
||||||
|
const totalAuctionPrice = Number(auction.maxPrice.quantity) * auction.numProviders;
|
||||||
|
|
||||||
let amountToBeReturned;
|
let amountToBeReturned;
|
||||||
if (winningDeployersPresent) {
|
if (winningDeployersPresent) {
|
||||||
amountToBeReturned = auction.winnerPrice * auction.numProviders;
|
amountToBeReturned = totalAuctionPrice - auction.winnerAddresses.length * Number(auction.winnerPrice.quantity);
|
||||||
} else {
|
} else {
|
||||||
amountToBeReturned = auction.maxPrice * auction.numProviders;
|
amountToBeReturned = totalAuctionPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.laconicRegistry.sendTokensToAccount(
|
if (amountToBeReturned !== 0) {
|
||||||
project.paymentAddress,
|
await this.laconicRegistry.sendTokensToAccount(
|
||||||
amountToBeReturned.toString()
|
project.paymentAddress,
|
||||||
);
|
amountToBeReturned.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDeployers(): Promise<Deployer[]> {
|
async getDeployers(): Promise<Deployer[]> {
|
||||||
@ -1384,7 +1427,8 @@ export class Service {
|
|||||||
const deployerId = record.id;
|
const deployerId = record.id;
|
||||||
const deployerLrn = record.names[0];
|
const deployerLrn = record.names[0];
|
||||||
const deployerApiUrl = record.attributes.apiUrl;
|
const deployerApiUrl = record.attributes.apiUrl;
|
||||||
const minimumPayment = record.attributes.minimumPayment
|
const minimumPayment = record.attributes.minimumPayment;
|
||||||
|
const paymentAddress = record.attributes.paymentAddress;
|
||||||
const baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1);
|
const baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1);
|
||||||
|
|
||||||
const deployerData = {
|
const deployerData = {
|
||||||
@ -1392,7 +1436,8 @@ export class Service {
|
|||||||
deployerId,
|
deployerId,
|
||||||
deployerApiUrl,
|
deployerApiUrl,
|
||||||
baseDomain,
|
baseDomain,
|
||||||
minimumPayment
|
minimumPayment,
|
||||||
|
paymentAddress
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Update deployers table in a separate job
|
// TODO: Update deployers table in a separate job
|
||||||
|
@ -173,17 +173,13 @@ const Configure = () => {
|
|||||||
|
|
||||||
const handleFormSubmit = useCallback(
|
const handleFormSubmit = useCallback(
|
||||||
async (createFormData: FieldValues) => {
|
async (createFormData: FieldValues) => {
|
||||||
if (!selectedAccount) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const senderAddress = selectedAccount;
|
|
||||||
const deployerLrn = createFormData.lrn;
|
const deployerLrn = createFormData.lrn;
|
||||||
const deployer = deployers.find(
|
const deployer = deployers.find(
|
||||||
(deployer) => deployer.deployerLrn === deployerLrn,
|
(deployer) => deployer.deployerLrn === deployerLrn,
|
||||||
);
|
);
|
||||||
|
|
||||||
let amount: string;
|
let amount: string;
|
||||||
|
let senderAddress: string;
|
||||||
let txHash: string;
|
let txHash: string;
|
||||||
if (createFormData.option === 'LRN' && !deployer?.minimumPayment) {
|
if (createFormData.option === 'LRN' && !deployer?.minimumPayment) {
|
||||||
toast({
|
toast({
|
||||||
@ -194,7 +190,12 @@ const Configure = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
txHash = '';
|
txHash = '';
|
||||||
|
senderAddress = '';
|
||||||
} else {
|
} else {
|
||||||
|
if (!selectedAccount) return;
|
||||||
|
|
||||||
|
senderAddress = selectedAccount.split(':')[2];
|
||||||
|
|
||||||
if (createFormData.option === 'LRN') {
|
if (createFormData.option === 'LRN') {
|
||||||
amount = deployer?.minimumPayment!;
|
amount = deployer?.minimumPayment!;
|
||||||
} else {
|
} else {
|
||||||
@ -220,8 +221,9 @@ const Configure = () => {
|
|||||||
const isTxHashValid = await verifyTx(
|
const isTxHashValid = await verifyTx(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
txHash,
|
txHash,
|
||||||
amount.toString(),
|
amountToBePaid.toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isTxHashValid === false) {
|
if (isTxHashValid === false) {
|
||||||
console.error('Invalid Tx hash', txHash);
|
console.error('Invalid Tx hash', txHash);
|
||||||
return;
|
return;
|
||||||
@ -243,7 +245,7 @@ const Configure = () => {
|
|||||||
const projectId = await createProject(
|
const projectId = await createProject(
|
||||||
createFormData,
|
createFormData,
|
||||||
environmentVariables,
|
environmentVariables,
|
||||||
senderAddress.split(':')[2],
|
senderAddress,
|
||||||
txHash,
|
txHash,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -442,7 +442,7 @@ export class GQLClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verifyTx(txHash: string, amount: string, senderAddress: string): Promise<boolean> {
|
async verifyTx(txHash: string, amount: string, senderAddress: string): Promise<boolean> {
|
||||||
const { data: verifyTx } = await this.client.query({
|
const { data } = await this.client.query({
|
||||||
query: queries.verifyTx,
|
query: queries.verifyTx,
|
||||||
variables: {
|
variables: {
|
||||||
txHash,
|
txHash,
|
||||||
@ -451,6 +451,6 @@ export class GQLClient {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return verifyTx;
|
return data.verifyTx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user