diff --git a/modules/bitgo/src/v2/coinFactory.ts b/modules/bitgo/src/v2/coinFactory.ts index 28b9065f59..ec97d536fc 100644 --- a/modules/bitgo/src/v2/coinFactory.ts +++ b/modules/bitgo/src/v2/coinFactory.ts @@ -25,6 +25,7 @@ import { Sol, Stx, Susd, + FiatUsd, Talgo, TavaxC, Tbch, @@ -45,6 +46,7 @@ import { Tsol, Tstx, Tsusd, + TfiatUsd, Ttrx, Xtz, Txtz, @@ -129,6 +131,8 @@ function getCoinConstructors(): Map { registerCoinConstructor(m, 'ofc', Ofc.createInstance); registerCoinConstructor(m, 'susd', Susd.createInstance); registerCoinConstructor(m, 'tsusd', Tsusd.createInstance); + registerCoinConstructor(m, 'fiatusd', FiatUsd.createInstance); + registerCoinConstructor(m, 'tfiatusd', TfiatUsd.createInstance); registerCoinConstructor(m, 'cspr', Cspr.createInstance); registerCoinConstructor(m, 'tcspr', Tcspr.createInstance); registerCoinConstructor(m, 'stx', Stx.createInstance); diff --git a/modules/bitgo/src/v2/coins/fiatusd.ts b/modules/bitgo/src/v2/coins/fiatusd.ts new file mode 100644 index 0000000000..44178ec8ae --- /dev/null +++ b/modules/bitgo/src/v2/coins/fiatusd.ts @@ -0,0 +1,76 @@ +/** + * @prettier + */ +import { BitGo } from '../../bitgo'; +import { + BaseCoin, + KeyPair, + ParsedTransaction, + ParseTransactionOptions, + SignedTransaction, + SignTransactionOptions, + VerifyAddressOptions, + VerifyTransactionOptions, +} from '../baseCoin'; +import { MethodNotImplementedError } from '../../errors'; + +export class FiatUsd extends BaseCoin { + static createInstance(bitgo: BitGo): BaseCoin { + return new FiatUsd(bitgo); + } + + /** + * Returns the factor between the base unit and its smallest subdivison + * @return {number} + */ + getBaseFactor() { + return 1e2; + } + + getChain() { + return 'fiatusd'; + } + + getFamily() { + return 'fiat'; + } + + getFullName() { + return 'USD Dollar'; + } + + /** + * Return whether the given m of n wallet signers/ key amounts are valid for the coin + */ + isValidMofNSetup({ m, n }: { m: number; n: number }) { + return m === 0 && n === 0; + } + + isValidAddress(address: string): boolean { + throw new MethodNotImplementedError(); + } + + generateKeyPair(seed?: Buffer): KeyPair { + throw new MethodNotImplementedError(); + } + + isValidPub(pub: string): boolean { + throw new MethodNotImplementedError(); + } + + async parseTransaction(params: ParseTransactionOptions): Promise { + return {}; + } + + isWalletAddress(params: VerifyAddressOptions): boolean { + throw new MethodNotImplementedError(); + } + + async verifyTransaction(params: VerifyTransactionOptions): Promise { + return true; + } + + async signTransaction(params: SignTransactionOptions = {}): Promise { + throw new MethodNotImplementedError(); + } +} diff --git a/modules/bitgo/src/v2/coins/index.ts b/modules/bitgo/src/v2/coins/index.ts index c504e71f34..e6c5f31bae 100644 --- a/modules/bitgo/src/v2/coins/index.ts +++ b/modules/bitgo/src/v2/coins/index.ts @@ -21,6 +21,7 @@ export { OfcToken } from './ofcToken'; export { Rbtc } from './rbtc'; export { StellarToken } from './stellarToken'; export { Susd } from './susd'; +export { FiatUsd } from './fiatusd'; export { Talgo } from './talgo'; export { Tbch } from './tbch'; export { Tbsv } from './tbsv'; @@ -36,6 +37,7 @@ export { Tltc } from './tltc'; export { Trbtc } from './trbtc'; export { Trx } from './trx'; export { Tsusd } from './tsusd'; +export { TfiatUsd } from './tfiatusd'; export { Ttrx } from './ttrx'; export { Hbar } from './hbar'; export { Thbar } from './thbar'; diff --git a/modules/bitgo/src/v2/coins/tfiatusd.ts b/modules/bitgo/src/v2/coins/tfiatusd.ts new file mode 100644 index 0000000000..2c1174da30 --- /dev/null +++ b/modules/bitgo/src/v2/coins/tfiatusd.ts @@ -0,0 +1,20 @@ +/** + * @prettier + */ +import { BitGo } from '../../bitgo'; +import { BaseCoin } from '../baseCoin'; +import { FiatUsd } from './fiatusd'; + +export class TfiatUsd extends FiatUsd { + static createInstance(bitgo: BitGo): BaseCoin { + return new TfiatUsd(bitgo); + } + + getChain() { + return 'tfiatusd'; + } + + getFullName() { + return 'Testnet US Dollar'; + } +} diff --git a/modules/bitgo/test/v2/unit/coins/fiat.ts b/modules/bitgo/test/v2/unit/coins/fiat.ts new file mode 100644 index 0000000000..f4850b866a --- /dev/null +++ b/modules/bitgo/test/v2/unit/coins/fiat.ts @@ -0,0 +1,16 @@ +import 'should'; +import { CoinFamily, coins, NetworkType } from '@bitgo/statics'; + +function executeTestForFiatCoins(coin, expectedNetwork) { + describe(`Fiat coin: ${coin.name}`, function () { + it('has expected network type', function () { + coin.network.type.should.eql(expectedNetwork); + }); + }); +} + +const fiatCoins = coins.filter(coin => !coin.isToken && coin.family === CoinFamily.FIAT); +const mainnetFiatCoins = fiatCoins.filter(coin => coin.name.startsWith('fiat')); +const testnetFiatCoins = fiatCoins.filter(coin => coin.name.startsWith('tfiat')); +mainnetFiatCoins.forEach(coin => executeTestForFiatCoins(coin, NetworkType.MAINNET)); +testnetFiatCoins.forEach(coin => executeTestForFiatCoins(coin, NetworkType.TESTNET)); diff --git a/modules/statics/src/account.ts b/modules/statics/src/account.ts index 9360334f4d..39b17669c3 100644 --- a/modules/statics/src/account.ts +++ b/modules/statics/src/account.ts @@ -76,6 +76,12 @@ export interface SolCoinConstructorOptions extends AccountConstructorOptions { tokenAddress: string; } +type FiatCoinName = `fiat${string}` | `tfiat${string}`; +export interface FiatCoinConstructorOptions extends AccountConstructorOptions { + name: FiatCoinName; + kind: CoinKind; +} + export interface ContractAddress extends String { __contractaddress_phantom__: never; } @@ -280,6 +286,29 @@ export class AvaxERC20Token extends ContractAddressDefinedToken { } } +/** + * Fiat currencies, such as USD, EUR, or YEN. + */ +export class FiatCoin extends BaseCoin { + public static readonly DEFAULT_FEATURES = [...AccountCoin.DEFAULT_FEATURES]; + + public readonly network: BaseNetwork; + + constructor(options: FiatCoinConstructorOptions) { + super(options); + + this.network = options.network; + } + + protected requiredFeatures(): Set { + return new Set([CoinFeature.ACCOUNT_MODEL]); + } + + protected disallowedFeatures(): Set { + return new Set([CoinFeature.UNSPENT_MODEL]); + } +} + /** * Factory function for account coin instances. * @@ -288,11 +317,11 @@ export class AvaxERC20Token extends ContractAddressDefinedToken { * @param network Network object for this coin * @param decimalPlaces Number of decimal places this coin supports (divisibility exponent) * @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin. + * @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin` + * @param primaryKeyCurve? The elliptic curve for this chain/token * @param prefix? Optional coin prefix. Defaults to empty string * @param suffix? Optional coin suffix. Defaults to coin name. * @param isToken? Whether or not this account coin is a token of another coin - * @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin` - * @param primaryKeyCurve The elliptic curve for this chain/token */ export function account( name: string, @@ -1132,3 +1161,48 @@ export function tavaxErc20( primaryKeyCurve ); } + +/** + * Factory function for fiat coin instances. + * + * @param name unique identifier of the coin, should start with 'fiat' or 'tfiat' + * @param fullName Complete human-readable name of the coin + * @param network Network object for this coin + * @param decimalPlaces Number of decimal places this coin supports (divisibility exponent) + * @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin. + * @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `FiatCoin` + * @param primaryKeyCurve? The elliptic curve for this chain/token + * @param prefix? Optional coin prefix. Defaults to empty string + * @param suffix? Optional coin suffix. Defaults to coin name. + * @param isToken? Whether or not this coin is a token of another coin + * @param kind? Differentiates coins which represent fiat assets from those which represent crypto assets + */ +export function fiat( + name: FiatCoinName, + fullName: string, + network: BaseNetwork, + decimalPlaces: number, + asset: UnderlyingAsset, + features: CoinFeature[] = FiatCoin.DEFAULT_FEATURES, + primaryKeyCurve: KeyCurve = KeyCurve.Secp256k1, + prefix = '', + suffix: string = name.toUpperCase(), + isToken = false, + kind: CoinKind = CoinKind.FIAT +) { + return Object.freeze( + new FiatCoin({ + name, + fullName, + network, + prefix, + suffix, + features, + decimalPlaces, + isToken, + asset, + primaryKeyCurve, + kind, + }) + ); +} diff --git a/modules/statics/src/coins.ts b/modules/statics/src/coins.ts index d35878cc45..12e76dbf76 100644 --- a/modules/statics/src/coins.ts +++ b/modules/statics/src/coins.ts @@ -21,6 +21,7 @@ import { tsolToken, terc721, erc721, + fiat, } from './account'; import { CoinFeature, CoinKind, KeyCurve, UnderlyingAsset } from './base'; import { CoinMap } from './map'; @@ -1544,4 +1545,6 @@ export const coins = CoinMap.fromCoins([ UnderlyingAsset.SLND, AccountCoin.DEFAULT_FEATURES ), + fiat('fiatusd', 'US Dollar', Networks.main.fiat, 2, UnderlyingAsset.USD), + fiat('tfiatusd', 'Testnet US Dollar', Networks.test.fiat, 2, UnderlyingAsset.USD), ]); diff --git a/modules/statics/src/networks.ts b/modules/statics/src/networks.ts index 4856ded27b..9c059fd9a8 100644 --- a/modules/statics/src/networks.ts +++ b/modules/statics/src/networks.ts @@ -510,13 +510,13 @@ class SUSDTestnet extends Testnet implements AccountNetwork { explorerUrl = undefined; } -class FiatTestnet extends Testnet implements AccountNetwork { +class FiatTestnet extends Testnet implements BaseNetwork { name = 'FiatTestnet'; family = CoinFamily.FIAT; explorerUrl = undefined; } -class Fiat extends Mainnet implements AccountNetwork { +class Fiat extends Mainnet implements BaseNetwork { name = 'Fiat'; family = CoinFamily.FIAT; explorerUrl = undefined; diff --git a/modules/statics/test/unit/coins.ts b/modules/statics/test/unit/coins.ts index 3ba5704bda..e1cc846cac 100644 --- a/modules/statics/test/unit/coins.ts +++ b/modules/statics/test/unit/coins.ts @@ -28,7 +28,7 @@ coins.forEach((coin, coinName) => { coin.name.should.eql(coinName); }); - if (!coin.isToken) { + if (!coin.isToken && coin.family !== CoinFamily.FIAT) { it(`has expected network type`, function () { coin.network.type.should.eql(coin.name === coin.family ? NetworkType.MAINNET : NetworkType.TESTNET); });