Implement payments for app deployments #17
@ -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[];
|
||||||
}
|
}
|
||||||
|
@ -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!
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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',
|
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: {
|
||||||
|
@ -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',
|
||||||
|
@ -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
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -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 = {
|
||||||
|
Loading…
Reference in New Issue
Block a user