Implement payments for app deployments #17
@ -15,6 +15,9 @@ export class Deployer {
|
||||
@Column('varchar')
|
||||
baseDomain!: string;
|
||||
|
||||
@Column('varchar', { nullable: true })
|
||||
minimumPayment!: string | null;
|
||||
|
||||
@ManyToMany(() => Project, (project) => project.deployers)
|
||||
projects!: Project[];
|
||||
}
|
||||
|
@ -139,6 +139,7 @@ type Deployer {
|
||||
deployerLrn: String!
|
||||
deployerId: String!
|
||||
deployerApiUrl: String!
|
||||
minimumPayment: String
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
}
|
||||
|
@ -1384,13 +1384,15 @@ export class Service {
|
||||
const deployerId = record.id;
|
||||
const deployerLrn = record.names[0];
|
||||
const deployerApiUrl = record.attributes.apiUrl;
|
||||
const minimumPayment = record.attributes.minimumPayment
|
||||
const baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1);
|
||||
|
||||
const deployerData = {
|
||||
deployerLrn,
|
||||
deployerId,
|
||||
deployerApiUrl,
|
||||
baseDomain
|
||||
baseDomain,
|
||||
minimumPayment
|
||||
};
|
||||
|
||||
// TODO: Update deployers table in a separate job
|
||||
|
@ -94,6 +94,7 @@ export interface DeployerRecord {
|
||||
expiryTime: string;
|
||||
attributes: {
|
||||
apiUrl: string;
|
||||
minimumPayment: string | null;
|
||||
name: string;
|
||||
paymentAddress: string;
|
||||
publicKey: string;
|
||||
|
@ -152,41 +152,62 @@ const Configure = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// const verifyTx = async (
|
||||
// senderAddress: string,
|
||||
// txHash: string,
|
||||
// ): Promise<boolean> => {
|
||||
// const isValid = await client.verifyTx(
|
||||
// txHash,
|
||||
// `${amount.toString()}alnt`,
|
||||
// senderAddress,
|
||||
// );
|
||||
// return isValid;
|
||||
// };
|
||||
const verifyTx = async (
|
||||
senderAddress: string,
|
||||
txHash: string,
|
||||
amount: string
|
||||
): Promise<boolean> => {
|
||||
const isValid = await client.verifyTx(
|
||||
txHash,
|
||||
`${amount.toString()}alnt`,
|
||||
senderAddress,
|
||||
);
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const handleFormSubmit = useCallback(
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
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(
|
||||
selectedAccount,
|
||||
String(amount),
|
||||
amount,
|
||||
);
|
||||
|
||||
console.log(txHash);
|
||||
if(!txHash) {
|
||||
console.error('Tx not successful');
|
||||
return;
|
||||
}
|
||||
|
||||
// const isTxHashValid = verifyTx(senderAddress, txHash);
|
||||
// if (!isTxHashValid) {
|
||||
// console.error("Invalid Tx hash", txHash)
|
||||
// return
|
||||
// }
|
||||
const isTxHashValid = verifyTx(senderAddress, txHash, amount.toString());
|
||||
if (!isTxHashValid) {
|
||||
console.error("Invalid Tx hash", txHash)
|
||||
return
|
||||
}
|
||||
|
||||
const environmentVariables = createFormData.variables.map(
|
||||
(variable: any) => {
|
||||
@ -352,7 +373,7 @@ const Configure = () => {
|
||||
key={deployer.deployerLrn}
|
||||
value={deployer.deployerLrn}
|
||||
>
|
||||
{deployer.deployerLrn}
|
||||
{deployer.deployerLrn} {deployer.minimumPayment}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -40,6 +40,7 @@ const deployment: Deployment = {
|
||||
deployerApiUrl: 'https://webapp-deployer-api.example.com',
|
||||
deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu',
|
||||
deployerLrn: 'lrn://example/deployers/webapp-deployer-api.example.com',
|
||||
minimumPayment: '1000units'
|
||||
},
|
||||
status: DeploymentStatus.Ready,
|
||||
createdBy: {
|
||||
|
@ -106,6 +106,7 @@ export const deployment0: Deployment = {
|
||||
deployerApiUrl: 'https://webapp-deployer-api.example.com',
|
||||
deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu',
|
||||
deployerLrn: 'lrn://deployer.apps.snowballtools.com ',
|
||||
minimumPayment: '1000units'
|
||||
},
|
||||
applicationDeploymentRequestId:
|
||||
'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize',
|
||||
@ -132,6 +133,7 @@ export const project: Project = {
|
||||
deployerApiUrl: 'https://webapp-deployer-api.example.com',
|
||||
deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu',
|
||||
deployerLrn: 'lrn://deployer.apps.snowballtools.com ',
|
||||
minimumPayment: '1000units'
|
||||
},
|
||||
],
|
||||
paymentAddress: '0x657868687686rb4787987br8497298r79284797487',
|
||||
|
@ -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;
|
||||
}
|
@ -28,6 +28,7 @@ query ($projectId: String!) {
|
||||
deployerLrn
|
||||
deployerId
|
||||
deployerApiUrl
|
||||
minimumPayment
|
||||
}
|
||||
paymentAddress
|
||||
txHash
|
||||
@ -86,6 +87,7 @@ query ($organizationSlug: String!) {
|
||||
deployerLrn
|
||||
deployerId
|
||||
deployerApiUrl
|
||||
minimumPayment
|
||||
}
|
||||
paymentAddress
|
||||
txHash
|
||||
@ -152,6 +154,7 @@ query ($projectId: String!) {
|
||||
deployerLrn
|
||||
deployerId
|
||||
deployerApiUrl
|
||||
minimumPayment
|
||||
}
|
||||
environment
|
||||
isCurrent
|
||||
@ -215,6 +218,7 @@ query ($searchText: String!) {
|
||||
deployerLrn
|
||||
deployerId
|
||||
deployerApiUrl
|
||||
minimumPayment
|
||||
}
|
||||
paymentAddress
|
||||
txHash
|
||||
@ -320,6 +324,7 @@ query {
|
||||
deployerLrn
|
||||
deployerId
|
||||
deployerApiUrl
|
||||
minimumPayment
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -119,6 +119,7 @@ export type Deployer = {
|
||||
deployerLrn: string;
|
||||
deployerId: string;
|
||||
deployerApiUrl: string;
|
||||
minimumPayment: string | null;
|
||||
}
|
||||
|
||||
export type OrganizationMember = {
|
||||
|
Loading…
Reference in New Issue
Block a user