Skip to content

Commit

Permalink
feat: add avaxc token support
Browse files Browse the repository at this point in the history
  • Loading branch information
alia-bitgo committed Dec 7, 2023
1 parent 7b909d0 commit 347afa0
Show file tree
Hide file tree
Showing 7 changed files with 538 additions and 1 deletion.
68 changes: 68 additions & 0 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion electron/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Tia, Ttia } from '@bitgo/sdk-coin-tia';
import { AbstractUtxoCoin } from '@bitgo/abstract-utxo';
import { BitGoAPI } from '@bitgo/sdk-api';
import { Ada, Tada } from '@bitgo/sdk-coin-ada';
import { AvaxC, TavaxC } from '@bitgo/sdk-coin-avaxc';
import { AvaxC, TavaxC, AvaxCToken } from '@bitgo/sdk-coin-avaxc';
import { Bch } from '@bitgo/sdk-coin-bch';
import { Bcha } from '@bitgo/sdk-coin-bcha';
import { Bsv } from '@bitgo/sdk-coin-bsv';
Expand Down Expand Up @@ -130,6 +130,9 @@ sdk.register('tcoreum', Tcoreum.createInstance);
Erc20Token.createTokenConstructors().forEach(({ name, coinConstructor }) => {
sdk.register(name, coinConstructor);
});
AvaxCToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
sdk.register(name, coinConstructor);
});

function handleSdkError(e: unknown): string {
if (typeof e === 'string' && e !== null) {
Expand Down
147 changes: 147 additions & 0 deletions src/containers/BuildUnsignedSweepCoin/AvalancheCTokenForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { Form, FormikHelpers, FormikProvider, useFormik } from 'formik';
import { Link } from 'react-router-dom';
import * as Yup from 'yup';
import { Button, FormikTextfield } from '~/components';

const validationSchema = Yup.object({
backupKey: Yup.string().required(),
backupKeyId: Yup.string(),
gasLimit: Yup.number()
.typeError('Gas limit must be a number')
.integer()
.positive('Gas limit must be a positive integer')
.required(),
gasPrice: Yup.number()
.typeError('Gas price must be a number')
.integer()
.positive('Gas price must be a positive integer')
.required(),
recoveryDestination: Yup.string().required(),
tokenAddress: Yup.string().required(),
userKey: Yup.string().required(),
userKeyId: Yup.string(),
walletContractAddress: Yup.string().required(),
}).required();

export type AvalancheCTokenFormProps = {
onSubmit: (
values: AvalancheCTokenFormValues,
formikHelpers: FormikHelpers<AvalancheCTokenFormValues>
) => void | Promise<void>;
};

type AvalancheCTokenFormValues = Yup.Asserts<typeof validationSchema>;

export function AvalancheCTokenForm({ onSubmit }: AvalancheCTokenFormProps) {
const formik = useFormik<AvalancheCTokenFormValues>({
onSubmit,
initialValues: {
backupKey: '',
backupKeyId: '',
gasLimit: 500000,
gasPrice: 30,
recoveryDestination: '',
tokenAddress: '',
userKey: '',
userKeyId: '',
walletContractAddress: '',
},
validationSchema,
});

return (
<FormikProvider value={formik}>
<Form>
<h4 className="tw-text-body tw-font-semibold tw-border-b-0.5 tw-border-solid tw-border-gray-700 tw-mb-4">
Self-managed cold wallet details
</h4>
<div className="tw-mb-4">
<FormikTextfield
HelperText="Your user public key, as found on your recovery KeyCard."
Label="User Public Key"
name="userKey"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="Your user Key ID, as found on your KeyCard. Most wallets will not have this and you can leave it blank."
Label="User Key ID (optional)"
name="userKeyId"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="The backup public key for the wallet, as found on your recovery KeyCard."
Label="Backup Public Key"
name="backupKey"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="Your backup Key ID, as found on your KeyCard. Most wallets will not have this and you can leave it blank."
Label="Backup Key ID (optional)"
name="backupKeyId"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="The AVAXC address of the wallet contract. This is also the wallet's base address."
Label="Wallet Contract Address"
name="walletContractAddress"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="The address of the smart contract of the token to recover. This is unique to each token, and is NOT your wallet address."
Label="Token Contract Address"
name="tokenAddress"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="The address your recovery transaction will send to."
Label="Destination Address"
name="recoveryDestination"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="Gas limit for the AVAXC transaction. The value should be between 30,000 and 20,000,000. The default is 500,000 unit of gas."
Label="Gas Limit"
name="gasLimit"
Width="fill"
/>
</div>
<div className="tw-mb-4">
<FormikTextfield
HelperText="Gas price for the AVAXC transaction. The default is 30 Gwei."
Label="Gas Price"
name="gasPrice"
Width="fill"
/>
</div>
<div className="tw-flex tw-flex-col-reverse sm:tw-justify-between sm:tw-flex-row tw-gap-1 tw-mt-4">
<Button Tag={Link} to="/" Variant="secondary" Width="hug">
Cancel
</Button>
<Button
Variant="primary"
Width="hug"
type="submit"
Disabled={formik.isSubmitting}
disabled={formik.isSubmitting}
>
{formik.isSubmitting ? 'Recovering...' : 'Recover Funds'}
</Button>
</div>
</Form>
</FormikProvider>
);
}
74 changes: 74 additions & 0 deletions src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '~/helpers';
import { useLocalStorageState } from '~/hooks';
import { AvalancheCForm } from './AvalancheCForm';
import { AvalancheCTokenForm } from './AvalancheCTokenForm';
import { BitcoinCashForm } from './BitcoinCashForm';
import { BitcoinForm } from './BitcoinForm';
import { BitcoinABCForm } from './BitcoinABCForm';
Expand Down Expand Up @@ -328,6 +329,79 @@ function Form() {
}}
/>
);
case 'avaxcToken':
case 'tavaxcToken':
return (
<AvalancheCTokenForm
key={coin}
onSubmit={async (values, { setSubmitting }) => {
setAlert(undefined);
setSubmitting(true);
try {
await window.commands.setBitGoEnvironment(
bitGoEnvironment,
coin,
values.apiKey

Check failure on line 344 in src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx

View workflow job for this annotation

GitHub Actions / test

Property 'apiKey' does not exist on type 'AssertsShape<{ backupKey: RequiredStringSchema<string | undefined, AnyObject>; backupKeyId: StringSchema<string | undefined, AnyObject, string | undefined>; ... 6 more ...; walletContractAddress: RequiredStringSchema<...>; }>'.
);
const parentCoin = env === 'test' ? 'tavaxc' : 'avaxc';
const chainData = await window.queries.getChain(parentCoin);
const recoverData = await window.commands.recover(parentCoin, {
...(await updateKeysFromIds(coin, values)),
gasPrice: toWei(values.gasPrice),
bitgoKey: '',
ignoreAddressTypes: [],
});
assert(
isRecoveryTransaction(recoverData),
'Fully-signed recovery transaction not detected.'
);

const showSaveDialogData = await window.commands.showSaveDialog({
filters: [
{
name: 'Custom File Type',
extensions: ['json'],
},
],
defaultPath: `~/${chainData}-unsigned-sweep-${Date.now()}.json`,
});

if (!showSaveDialogData.filePath) {
throw new Error('No file path selected');
}

await window.commands.writeFile(
showSaveDialogData.filePath,
JSON.stringify(
includePubsInUnsignedSweep
? {
...recoverData,
...(await includePubsForToken(
values.tokenAddress.toLowerCase(),
coin,
values
)),
}
: recoverData,
null,
2
),
{ encoding: 'utf-8' }
);

navigate(
`/${bitGoEnvironment}/build-unsigned-sweep/${coin}/success`
);
} catch (err) {
if (err instanceof Error) {
setAlert(err.message);
} else {
console.error(err);
}
}
}}
/>
);
case 'xrp':
case 'txrp':
case 'xlm':
Expand Down
Loading

0 comments on commit 347afa0

Please sign in to comment.