Implement payments for app deployments #17

Merged
nabarun merged 27 commits from iv-integrate-payments into main 2024-10-28 09:46:19 +00:00
11 changed files with 60 additions and 139 deletions
Showing only changes of commit 9937547277 - Show all commits

View File

@ -15,6 +15,9 @@ export class Deployer {
@Column('varchar') @Column('varchar')
baseDomain!: string; baseDomain!: string;
@Column('varchar', { nullable: true })
minimumPayment!: string | null;
@ManyToMany(() => Project, (project) => project.deployers) @ManyToMany(() => Project, (project) => project.deployers)
projects!: Project[]; projects!: Project[];
} }

View File

@ -139,6 +139,7 @@ type Deployer {
deployerLrn: String! deployerLrn: String!
deployerId: String! deployerId: String!
deployerApiUrl: String! deployerApiUrl: String!
minimumPayment: String
createdAt: String! createdAt: String!
updatedAt: String! updatedAt: String!
} }

View File

@ -1384,13 +1384,15 @@ 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 baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1); const baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1);
const deployerData = { const deployerData = {
deployerLrn, deployerLrn,
deployerId, deployerId,
deployerApiUrl, deployerApiUrl,
baseDomain baseDomain,
minimumPayment
}; };
// TODO: Update deployers table in a separate job // TODO: Update deployers table in a separate job

View File

@ -94,6 +94,7 @@ export interface DeployerRecord {
expiryTime: string; expiryTime: string;
attributes: { attributes: {
apiUrl: string; apiUrl: string;
minimumPayment: string | null;
name: string; name: string;
paymentAddress: string; paymentAddress: string;
publicKey: string; publicKey: string;

View File

@ -152,41 +152,62 @@ const Configure = () => {
} }
}; };
// const verifyTx = async ( const verifyTx = async (
// senderAddress: string, senderAddress: string,
// txHash: string, txHash: string,
// ): Promise<boolean> => { amount: string
// const isValid = await client.verifyTx( ): Promise<boolean> => {
// txHash, const isValid = await client.verifyTx(
// `${amount.toString()}alnt`, txHash,
// senderAddress, `${amount.toString()}alnt`,
// ); senderAddress,
// return isValid; );
// }; return isValid;
};
const handleFormSubmit = useCallback( const handleFormSubmit = useCallback(
async (createFormData: FieldValues) => { async (createFormData: FieldValues) => {
// Send tx request to wallet -> amount = createFormData.maxPrice * createFormData.numProviders
// Get address of sender account(from wallet connect session) and txHash(result.signature)
if (!selectedAccount) { if (!selectedAccount) {
return; return;
} }
const senderAddress = selectedAccount; const senderAddress = selectedAccount;
const amount = createFormData.numProviders * createFormData.maxPrice; let amount: string;
if(createFormData.option === 'LRN') {
const deployerLrn = createFormData.lrn;
const deployer = deployers.find(deployer => deployer.deployerLrn === deployerLrn);
if (!deployer?.minimumPayment) {
toast({
id: 'no-payment-required',
title: 'No payment',
variant: 'info',
onDismiss: dismiss,
});
amount = ''
} else {
amount = deployer!.minimumPayment
}
} else {
amount = (createFormData.numProviders * createFormData.maxPrice).toString();
}
const txHash = await cosmosSendTokensHandler( const txHash = await cosmosSendTokensHandler(
selectedAccount, selectedAccount,
String(amount), amount,
); );
console.log(txHash); if(!txHash) {
console.error('Tx not successful');
return;
}
// const isTxHashValid = verifyTx(senderAddress, txHash); const isTxHashValid = verifyTx(senderAddress, txHash, amount.toString());
// if (!isTxHashValid) { if (!isTxHashValid) {
// console.error("Invalid Tx hash", txHash) console.error("Invalid Tx hash", txHash)
// return return
// } }
const environmentVariables = createFormData.variables.map( const environmentVariables = createFormData.variables.map(
(variable: any) => { (variable: any) => {
@ -352,7 +373,7 @@ const Configure = () => {
key={deployer.deployerLrn} key={deployer.deployerLrn}
value={deployer.deployerLrn} value={deployer.deployerLrn}
> >
{deployer.deployerLrn} {deployer.deployerLrn} {deployer.minimumPayment}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>

View File

@ -1,83 +0,0 @@
import { Button } from 'components/shared/Button';
import { LoaderIcon } from 'components/shared/CustomIcon';
import { KeyIcon } from 'components/shared/CustomIcon/KeyIcon';
import { InlineNotification } from 'components/shared/InlineNotification';
import { Input } from 'components/shared/Input';
import { WavyBorder } from 'components/shared/WavyBorder';
import { useState } from 'react';
import { IconRight } from 'react-day-picker';
import { useSnowball } from 'utils/use-snowball';
type Props = {
onDone: () => void;
};
export const CreatePasskey = ({}: Props) => {
const snowball = useSnowball();
const [name, setName] = useState('');
const auth = snowball.auth.passkey;
const loading = !!auth.state.loading;
async function createPasskey() {
await auth.register(name);
}
return (
<div>
<div className="self-stretch p-3 xs:p-6 flex-col justify-center items-center gap-5 flex">
<div className="w-16 h-16 p-2 bg-sky-100 rounded-[800px] justify-center items-center gap-2 inline-flex">
<KeyIcon />
</div>
<div>
<div className="self-stretch text-center text-sky-950 text-2xl font-medium font-display leading-loose">
Create a passkey
</div>
<div className="text-center text-slate-600 text-sm font-normal font-['Inter'] leading-tight">
Passkeys allow you to sign in securely without using passwords.
</div>
</div>
</div>
<WavyBorder className="self-stretch" variant="stroke" />
<div className="p-6 flex-col justify-center items-center gap-8 inline-flex">
<div className="self-stretch h-36 flex-col justify-center items-center gap-2 flex">
<div className="self-stretch h-[72px] flex-col justify-start items-start gap-2 flex">
<div className="self-stretch h-5 px-1 flex-col justify-start items-start gap-1 flex">
<div className="self-stretch text-sky-950 text-sm font-normal font-['Inter'] leading-tight">
Give it a name
</div>
</div>
<Input
value={name}
onInput={(e: any) => {
setName(e.target.value);
}}
/>
</div>
{auth.state.error ? (
<InlineNotification
title={auth.state.error.message}
variant="danger"
/>
) : (
<InlineNotification
title={`Once you press the "Create passkeys" button, you'll receive a prompt to create the passkey.`}
variant="info"
/>
)}
</div>
<Button
rightIcon={
loading ? <LoaderIcon className="animate-spin" /> : <IconRight />
}
className="self-stretch"
disabled={!name || loading}
onClick={createPasskey}
>
Create Passkey
</Button>
</div>
</div>
);
};

View File

@ -40,6 +40,7 @@ const deployment: Deployment = {
deployerApiUrl: 'https://webapp-deployer-api.example.com', deployerApiUrl: 'https://webapp-deployer-api.example.com',
deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu', deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu',
deployerLrn: 'lrn://example/deployers/webapp-deployer-api.example.com', deployerLrn: 'lrn://example/deployers/webapp-deployer-api.example.com',
minimumPayment: '1000units'
}, },
status: DeploymentStatus.Ready, status: DeploymentStatus.Ready,
createdBy: { createdBy: {

View File

@ -106,6 +106,7 @@ export const deployment0: Deployment = {
deployerApiUrl: 'https://webapp-deployer-api.example.com', deployerApiUrl: 'https://webapp-deployer-api.example.com',
deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu', deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu',
deployerLrn: 'lrn://deployer.apps.snowballtools.com ', deployerLrn: 'lrn://deployer.apps.snowballtools.com ',
minimumPayment: '1000units'
}, },
applicationDeploymentRequestId: applicationDeploymentRequestId:
'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize', 'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize',
@ -132,6 +133,7 @@ export const project: Project = {
deployerApiUrl: 'https://webapp-deployer-api.example.com', deployerApiUrl: 'https://webapp-deployer-api.example.com',
deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu', deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu',
deployerLrn: 'lrn://deployer.apps.snowballtools.com ', deployerLrn: 'lrn://deployer.apps.snowballtools.com ',
minimumPayment: '1000units'
}, },
], ],
paymentAddress: '0x657868687686rb4787987br8497298r79284797487', paymentAddress: '0x657868687686rb4787987br8497298r79284797487',

View File

@ -1,33 +0,0 @@
import { useEffect, useState } from 'react';
import { Snowball, SnowballChain } from '@snowballtools/js-sdk';
import {
// LitAppleAuth,
LitGoogleAuth,
LitPasskeyAuth,
} from '@snowballtools/auth-lit';
import { VITE_LIT_RELAY_API_KEY } from './constants';
export const snowball = Snowball.withAuth({
google: LitGoogleAuth.configure({
litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
}),
// apple: LitAppleAuth.configure({
// litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
// }),
passkey: LitPasskeyAuth.configure({
litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
}),
}).create({
initialChain: SnowballChain.sepolia,
});
export function useSnowball() {
const [state, setState] = useState(100);
useEffect(() => {
// Subscribe and directly return the unsubscribe function
return snowball.subscribe(() => setState(state + 1));
}, [state]);
return snowball;
}

View File

@ -28,6 +28,7 @@ query ($projectId: String!) {
deployerLrn deployerLrn
deployerId deployerId
deployerApiUrl deployerApiUrl
minimumPayment
} }
paymentAddress paymentAddress
txHash txHash
@ -86,6 +87,7 @@ query ($organizationSlug: String!) {
deployerLrn deployerLrn
deployerId deployerId
deployerApiUrl deployerApiUrl
minimumPayment
} }
paymentAddress paymentAddress
txHash txHash
@ -152,6 +154,7 @@ query ($projectId: String!) {
deployerLrn deployerLrn
deployerId deployerId
deployerApiUrl deployerApiUrl
minimumPayment
} }
environment environment
isCurrent isCurrent
@ -215,6 +218,7 @@ query ($searchText: String!) {
deployerLrn deployerLrn
deployerId deployerId
deployerApiUrl deployerApiUrl
minimumPayment
} }
paymentAddress paymentAddress
txHash txHash
@ -320,6 +324,7 @@ query {
deployerLrn deployerLrn
deployerId deployerId
deployerApiUrl deployerApiUrl
minimumPayment
} }
} }
`; `;

View File

@ -119,6 +119,7 @@ export type Deployer = {
deployerLrn: string; deployerLrn: string;
deployerId: string; deployerId: string;
deployerApiUrl: string; deployerApiUrl: string;
minimumPayment: string | null;
} }
export type OrganizationMember = { export type OrganizationMember = {