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
8 changed files with 67 additions and 61 deletions
Showing only changes of commit 9f2a97908a - Show all commits

View File

@ -265,7 +265,7 @@ type Query {
domains(projectId: String!, filter: FilterDomainsInput): [Domain] domains(projectId: String!, filter: FilterDomainsInput): [Domain]
deployers: [Deployer] deployers: [Deployer]
address: String! address: String!
verifyTx(txhash: String!, amount: String!, senderAddress: String!): Boolean! verifyTx(txHash: String!, amount: String!, senderAddress: String!): Boolean!
} }
type Mutation { type Mutation {

View File

@ -1406,8 +1406,8 @@ export class Service {
return this.laconicRegistry.getAddress(); return this.laconicRegistry.getAddress();
} }
async verifyTx(txhash: string, amountSent: string, senderAddress: string): Promise<boolean> { async verifyTx(txHash: string, amountSent: string, senderAddress: string): Promise<boolean> {
const txResponse = await this.laconicRegistry.getTxResponse(txhash); const txResponse = await this.laconicRegistry.getTxResponse(txHash);
if (!txResponse) { if (!txResponse) {
log('Transaction response not found'); log('Transaction response not found');
return false; return false;

View File

@ -56,7 +56,12 @@ const Configure = () => {
const client = useGQLClient(); const client = useGQLClient();
const methods = useForm<ConfigureFormValues>({ const methods = useForm<ConfigureFormValues>({
defaultValues: { option: 'Auction' }, defaultValues: {
option: 'Auction',
maxPrice: '0',
lrn: '',
numProviders: 0,
},
}); });
const selectedOption = methods.watch('option'); const selectedOption = methods.watch('option');
@ -68,7 +73,7 @@ const Configure = () => {
data: FieldValues, data: FieldValues,
envVariables: AddEnvironmentVariableInput[], envVariables: AddEnvironmentVariableInput[],
senderAddress: string, senderAddress: string,
txHash: string txHash: string,
): Promise<string> => { ): Promise<string> => {
setIsLoading(true); setIsLoading(true);
let projectId: string | null = null; let projectId: string | null = null;
@ -94,7 +99,7 @@ const Configure = () => {
name, name,
isPrivate, isPrivate,
paymentAddress: senderAddress, paymentAddress: senderAddress,
txHash txHash,
}; };
const { addProjectFromTemplate } = await client.addProjectFromTemplate( const { addProjectFromTemplate } = await client.addProjectFromTemplate(
@ -115,7 +120,7 @@ const Configure = () => {
repository: fullName!, repository: fullName!,
template: 'webapp', template: 'webapp',
paymentAddress: senderAddress, paymentAddress: senderAddress,
txHash txHash,
}, },
lrn, lrn,
auctionParams, auctionParams,
@ -166,7 +171,7 @@ const Configure = () => {
createFormData, createFormData,
environmentVariables, environmentVariables,
senderAddress, senderAddress,
txHash txHash,
); );
await client.getEnvironmentVariables(projectId); await client.getEnvironmentVariables(projectId);
@ -305,7 +310,11 @@ const Configure = () => {
control={methods.control} control={methods.control}
rules={{ required: true }} rules={{ required: true }}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<Input type="number" value={value} onChange={onChange} /> <Input
type="number"
value={value}
onChange={(e) => onChange(e)}
/>
)} )}
/> />
</div> </div>
@ -350,7 +359,7 @@ const Configure = () => {
</div> </div>
</form> </form>
</FormProvider> </FormProvider>
<ConnectWallet/> <ConnectWallet numProviders={methods.watch('numProviders') || 0} />
</div> </div>
</div> </div>
); );

View File

@ -1,39 +1,46 @@
import { useMemo, useState, useCallback } from 'react';
import { Select, Option } from '@snowballtools/material-tailwind-react-fork';
import { Button } from '../../shared/Button'; import { Button } from '../../shared/Button';
import { useWalletConnectClient } from 'context/WalletConnectContext'; import { useWalletConnectClient } from 'context/WalletConnectContext';
import { Select, Option } from '@snowballtools/material-tailwind-react-fork';
import { useGQLClient } from 'context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
import { useCallback, useEffect, useState } from 'react';
const TEST_AMOUNT = "10000" const ConnectWallet = ({ numProviders }: { numProviders: number }) => {
const ConnectWallet = () => {
const { onConnect, accounts, signClient, session } = useWalletConnectClient(); const { onConnect, accounts, signClient, session } = useWalletConnectClient();
const client = useGQLClient(); const client = useGQLClient();
const [selectedAccount, setSelectedAccount] = useState<{ const [selectedAccount, setSelectedAccount] = useState<string>();
address: string; const [isTxValid, setIsTxValid] = useState<boolean>(false);
balance?: string;
}>(); const amount = useMemo(() => numProviders * 10000, [numProviders]);
const [txHash, setTxHash] = useState<string>();
const [snowballAddress, setSnowballAddress] = useState<string>();
const handleConnect = async () => { const handleConnect = async () => {
await onConnect(); await onConnect();
}; };
const verifyTx = async (
senderAddress: string,
txHash: string,
): Promise<boolean> => {
const isValid = await client.verifyTx(
txHash,
`${amount.toString()}alnt`,
senderAddress,
);
return isValid;
};
const cosmosSendTokensHandler = useCallback( const cosmosSendTokensHandler = useCallback(
async (senderAddress: string, amount: string) => { async (selectedAccount: string) => {
if (!signClient || !session || !selectedAccount || !snowballAddress) { if (!signClient || !session || !selectedAccount) {
console.log({signClient, session, selectedAccount})
return; return;
} }
const chainId = selectedAccount.address.split(':')[1]; const chainId = selectedAccount.split(':')[1];
const senderAddress = selectedAccount.split(':')[2];
const snowballAddress = await client.getAddress();
try { try {
const result: { const result: { signature: string } = await signClient.request({
signature: string;
} = await signClient.request({
topic: session.topic, topic: session.topic,
chainId: `cosmos:${chainId}`, chainId: `cosmos:${chainId}`,
request: { request: {
@ -47,47 +54,33 @@ const ConnectWallet = () => {
], ],
}, },
}); });
if (!result) { if (!result) {
throw new Error('Error completing transaction'); throw new Error('Error completing transaction');
} }
setTxHash(result.signature); const isValid = await verifyTx(senderAddress, result.signature);
setIsTxValid(isValid);
} catch (error: any) { } catch (error: any) {
throw error; throw error;
} }
}, },
[session, signClient, selectedAccount, snowballAddress], [session, signClient, selectedAccount, amount],
); );
useEffect(() => {
console.log(txHash)
}, [txHash])
const fetchSnowballAddress = useCallback(async() => {
const address = await client.getAddress();
setSnowballAddress(address);
console.log(address)
}, [client])
useEffect(() => {
fetchSnowballAddress()
}, [])
return ( return (
<> <div className="p-4 bg-slate-100 rounded-lg mb-6">
{!accounts ? ( {!accounts ? (
<Button onClick={handleConnect}>Connect Wallet</Button> <Button onClick={handleConnect}>Connect Wallet</Button>
) : isTxValid ? (
<div className="mt-4 text-green-600">Tx successful!</div>
) : ( ) : (
<div> <div>
<Select <Select
label="Select Account" label="Select Account"
defaultValue={accounts[0].address} defaultValue={accounts[0].address}
onChange={(value) => { onChange={(value) => {
setSelectedAccount({ setSelectedAccount(value);
address: value!,
});
}} }}
> >
{accounts.map((account, index) => ( {accounts.map((account, index) => (
@ -96,10 +89,14 @@ const ConnectWallet = () => {
</Option> </Option>
))} ))}
</Select> </Select>
<Button onClick={() => cosmosSendTokensHandler(selectedAccount!.address.split(":")[2], TEST_AMOUNT)}>Pay</Button> <Button
onClick={() => cosmosSendTokensHandler(selectedAccount || '')}
>
Pay
</Button>
</div> </div>
)} )}
</> </div>
); );
}; };

View File

@ -25,7 +25,7 @@ interface ClientInterface {
onConnect: () => Promise<void>; onConnect: () => Promise<void>;
onDisconnect: () => Promise<void>; onDisconnect: () => Promise<void>;
onSessionDelete: () => void; onSessionDelete: () => void;
accounts: { address: string; balance?: string }[] | undefined; accounts: { address: string }[] | undefined;
} }
const ClientContext = createContext({} as ClientInterface); const ClientContext = createContext({} as ClientInterface);

View File

@ -3,5 +3,5 @@ import { VITE_WALLET_CONNECT_ID } from 'utils/constants';
export const walletConnectModal = new WalletConnectModal({ export const walletConnectModal = new WalletConnectModal({
projectId: VITE_WALLET_CONNECT_ID!, projectId: VITE_WALLET_CONNECT_ID!,
chains:['cosmos:theta-testnet-001', 'cosmos:laconic_9000-1'], chains: ['cosmos:theta-testnet-001', 'cosmos:laconic_9000-1'],
}); });

View File

@ -441,21 +441,21 @@ export class GQLClient {
return data.address; return data.address;
} }
async verifyTx(txhash: string, amount: string, senderAddress: string): Promise<boolean> { async verifyTx(txHash: string, amount: string, senderAddress: string): Promise<boolean> {
console.log('Verifying Transaction with parameters:', { console.log('Verifying Transaction with parameters:', {
txhash, txHash,
amount, amount,
senderAddress senderAddress
}); });
const { data } = await this.client.query({ const { data: verifyTx } = await this.client.query({
query: queries.verifyTx, query: queries.verifyTx,
variables: { variables: {
txhash, txHash,
amount, amount,
senderAddress senderAddress
} }
}); });
return data; return verifyTx;
} }
} }

View File

@ -331,7 +331,7 @@ query {
`; `;
export const verifyTx = gql` export const verifyTx = gql`
query ($txhash: String!, $amount: String!, $senderAddress: String!) { query ($txHash: String!, $amount: String!, $senderAddress: String!) {
verifyTx(txhash: $txhash, amount: $amount, senderAddress: $senderAddress) verifyTx(txHash: $txHash, amount: $amount, senderAddress: $senderAddress)
} }
`; `;