Skip to content

Commit

Permalink
Merge pull request #1553 from balancednetwork/feat/implement-stellar-…
Browse files Browse the repository at this point in the history
…sponsorship

Add Stellar address sponsorship
  • Loading branch information
hetfly authored Jan 8, 2025
2 parents be7567d + b3d75c1 commit 6c52bb3
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 7 deletions.
154 changes: 154 additions & 0 deletions apps/web/src/app/components/StellarSponsorshipModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import React from 'react';

import { StellarXService, useXService } from '@balancednetwork/xwagmi';
import { BASE_FEE, Networks, Operation, TransactionBuilder } from '@balancednetwork/xwagmi';
import { Trans } from '@lingui/macro';
import axios from 'axios';
import { Flex } from 'rebass';

import { Typography } from '@/app/theme';
import { AnimatePresence, motion } from 'framer-motion';
import { TextButton } from '../Button';
import { StyledButton } from '../Button/StyledButton';
import { UnderlineText } from '../DropdownText';
import Modal from '../Modal';
import ModalContent from '../ModalContent';
import Spinner from '../Spinner';

const SPONSOR_URL = 'https://ciihnqaqiomjdoicuy5rgwmy5m0vxanz.lambda-url.us-east-1.on.aws';
const SPONSORING_ADDRESS = 'GCV5PJ4H57MZFRH5GM3E3CNFLWQURNFNIHQOYGRQ7JHGWJLAR2SFVZO6';

type StellarSponsorshipModalProps = {
address: string;
text: string;
};

const StellarSponsorshipModal = ({ text, address }: StellarSponsorshipModalProps) => {
const stellarXService = useXService('STELLAR') as unknown as StellarXService;
const [isLoading, setLoading] = React.useState(false);
const [isOpen, setOpen] = React.useState(false);
const [initiated, setInitiated] = React.useState(false);
const [success, setSuccess] = React.useState(false);
const handleDismiss = () => {
setOpen(false);
};

const handleToggle = () => {
setOpen(!isOpen);
};

const requestSponsorship = async () => {
if (!stellarXService) {
console.error('Stellar service not available');
return;
}
try {
const client = axios.create({
baseURL: SPONSOR_URL,
headers: {
'Content-Type': 'application/json',
},
});

//Sponsoring account
const sourceAccount = await stellarXService.server.loadAccount(SPONSORING_ADDRESS);

//Create the transaction to sponsor the user account creation
const transaction = new TransactionBuilder(sourceAccount, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
.addOperation(
Operation.beginSponsoringFutureReserves({
source: SPONSORING_ADDRESS,
sponsoredId: address,
}),
)
.addOperation(
Operation.createAccount({
destination: address,
startingBalance: '0',
}),
)
.addOperation(
Operation.endSponsoringFutureReserves({
source: address,
}),
)
.setTimeout(180)
.build();

const { signedTxXdr: signedTx } = await stellarXService.walletsKit.signTransaction(transaction.toXDR());

setLoading(true);
setInitiated(true);
const response = await client.post('/', { data: signedTx });

if (response.statusText === 'OK' && response.data) {
setSuccess(true);
setTimeout(() => {
handleDismiss();
}, 2000);
}
} catch (error) {
console.error('Error fetching Stellar sponsor transaction:', error);
throw error;
} finally {
setLoading(false);
}
};

return (
<>
<Typography color="primaryBright" onClick={handleToggle}>
<UnderlineText>{text}</UnderlineText>
</Typography>
<Modal isOpen={isOpen} onDismiss={handleDismiss}>
<ModalContent noMessages>
<Typography textAlign="center" color={'text'}>
<Trans>Activate Stellar wallet?</Trans>
</Typography>

<Typography pt={3} color={'text1'} textAlign="center">
<Trans>Sign a transaction to activate your wallet for free.</Trans>
</Typography>

<AnimatePresence>
{initiated && (
<motion.div
style={{ transform: 'translateY(8px)' }}
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 40 }}
>
<Spinner $centered success={success} />
</motion.div>
)}
</AnimatePresence>

<AnimatePresence>
{!success && (
<motion.div
key={'sponsorship-ctas'}
style={{ overflow: 'hidden' }}
initial={{ opacity: 1, height: 'auto' }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
>
<Flex justifyContent="center" mt="20px" pt="20px" className="border-top">
<TextButton onClick={handleDismiss}>
{isLoading ? <Trans>Close</Trans> : <Trans>Cancel</Trans>}
</TextButton>
<StyledButton disabled={isLoading} $loading={isLoading} onClick={requestSponsorship}>
{isLoading ? <Trans>Activating</Trans> : <Trans>Activate wallet</Trans>}
</StyledButton>
</Flex>
</motion.div>
)}
</AnimatePresence>
</ModalContent>
</Modal>
</>
);
};

export default StellarSponsorshipModal;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AutoColumn } from '@/app/components/Column';
import CurrencyInputPanel from '@/app/components/CurrencyInputPanel';
import { BrightPanel } from '@/app/components/Panel';
import { CurrencySelectionType, SelectorType } from '@/app/components/SearchModal/CurrencySearch';
import StellarSponsorshipModal from '@/app/components/StellarSponsorshipModal';
import { handleConnectWallet } from '@/app/components/WalletModal/WalletItem';
import { Typography } from '@/app/theme';
import FlipIcon from '@/assets/icons/horizontal-flip.svg';
Expand Down Expand Up @@ -202,9 +203,9 @@ export default function BridgeTransferForm({ openModal }) {
)}
</Flex>

{stellarValidation?.ok === false && stellarValidation.error && (
<Flex alignItems="center" justifyContent="center" mt={2}>
<Typography textAlign="center">{stellarValidation.error}</Typography>
{stellarValidation?.ok === false && stellarValidation.error && recipient && (
<Flex alignItems="center" justifyContent="center" mt={2} flexDirection="column">
<StellarSponsorshipModal text={'Activate your Stellar wallet.'} address={recipient} />
</Flex>
)}

Expand Down
7 changes: 4 additions & 3 deletions apps/web/src/app/pages/trade/xswap/_components/SwapPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { AutoColumn } from '@/app/components/Column';
import CurrencyInputPanel from '@/app/components/CurrencyInputPanel';
import { BrightPanel } from '@/app/components/Panel';
import { SelectorType } from '@/app/components/SearchModal/CurrencySearch';
import StellarSponsorshipModal from '@/app/components/StellarSponsorshipModal';
import { Typography } from '@/app/theme';
import FlipIcon from '@/assets/icons/flip.svg';
import { PRICE_IMPACT_WARNING_THRESHOLD } from '@/constants/misc';
Expand Down Expand Up @@ -240,9 +241,9 @@ export default function SwapPanel() {
/>
</Flex>

{stellarValidation?.ok === false && stellarValidation.error && (
<Flex alignItems="center" justifyContent="center" mt={2}>
<Typography textAlign="center">{stellarValidation.error}</Typography>
{stellarValidation?.ok === false && stellarValidation.error && recipient && (
<Flex alignItems="center" justifyContent="center" mt={2} flexDirection="column">
<StellarSponsorshipModal text={'Activate your Stellar wallet.'} address={recipient} />
</Flex>
)}

Expand Down
2 changes: 2 additions & 0 deletions packages/xwagmi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ export type * from './xcall/types';

export { useAccount, useSwitchChain } from 'wagmi';
export { useSignTransaction, useCurrentAccount, useCurrentWallet, useSuiClient } from '@mysten/dapp-kit';

export { BASE_FEE, Networks, Operation, TransactionBuilder } from '@stellar/stellar-sdk';
2 changes: 1 addition & 1 deletion packages/xwagmi/src/xchains/stellar/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function useValidateStellarAccount(address?: string | null): UseQueryResu
await stellarService.server.loadAccount(address);
return { ok: true };
} catch (e) {
return { ok: false, error: `Stellar wallet inactive. Add at least 1 XLM from an external source.` };
return { ok: false, error: `Stellar wallet inactive. Add at least 1 XLM from an external source` };
}
},
});
Expand Down

0 comments on commit 6c52bb3

Please sign in to comment.