Skip to content

Commit ecbf8a1

Browse files
BitGo Agentraj-bitgo
authored andcommitted
feat(statics): add TOKENIZED_EQUITY and BITGO_TOKENIZED_EQUITY CoinFeatures
Add two new values to the CoinFeature enum in @bitgo/statics: - TOKENIZED_EQUITY: economic-nature marker for any tokenized stock. Drives generic behavior (pricing off the underlying, asset-class reporting). May be set for BitGo-issued or third-party tokens. - BITGO_TOKENIZED_EQUITY: BitGo first-party permissioned goStocks marker. Drives whitelist-destination withdrawal validation and allowlist/segregation behavior. Requires TOKENIZED_EQUITY to also be set (invariant enforced in BaseCoin.validateOptions). Apply both flags to all goSPCX representations: sol:gospcx, tsol:gospcx, tsol:stggospcx (on-chain tokens) and ofcsol:gospcx, ofctsol:gospcx, ofctsol:stggospcx (OFC mirrors). No other goStock tokens receive these flags in this ticket; behavioral enforcement (withdrawal allowlist checks) is out of scope and tracked in TDD §13.4. Ticket: SCAAS-10372 Session-Id: a4128cc9-058b-4a15-ab67-160780260e8a Task-Id: a0f7361a-becc-4c21-ab18-99920c0c7a35
1 parent e759bf0 commit ecbf8a1

4 files changed

Lines changed: 113 additions & 7 deletions

File tree

modules/statics/src/base.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,20 @@ export enum CoinFeature {
602602
* This coin supports bring-your-own-validator (BYOV) staking
603603
*/
604604
BYOV_VALIDATOR = 'byov-validator',
605+
606+
/**
607+
* This coin is a tokenized equity (e.g. a tokenized stock). Drives generic
608+
* behavior such as pricing off the underlying and asset-class reporting.
609+
* May be set for both BitGo-issued and third-party tokenized equities.
610+
*/
611+
TOKENIZED_EQUITY = 'tokenized-equity',
612+
613+
/**
614+
* This coin is a BitGo first-party permissioned tokenized equity (a goStock).
615+
* Drives whitelist-destination withdrawal validation and allowlist/segregation
616+
* behavior. MUST only be set together with TOKENIZED_EQUITY.
617+
*/
618+
BITGO_TOKENIZED_EQUITY = 'bitgo-tokenized-equity',
605619
}
606620

607621
/**
@@ -4623,6 +4637,12 @@ export abstract class BaseCoin {
46234637
throw new MissingRequiredCoinFeatureError(options.name, Array.from(requiredFeatures));
46244638
}
46254639

4640+
// BITGO_TOKENIZED_EQUITY requires TOKENIZED_EQUITY to also be present
4641+
const featureSet = new Set(options.features);
4642+
if (featureSet.has(CoinFeature.BITGO_TOKENIZED_EQUITY) && !featureSet.has(CoinFeature.TOKENIZED_EQUITY)) {
4643+
throw new MissingRequiredCoinFeatureError(options.name, [CoinFeature.TOKENIZED_EQUITY]);
4644+
}
4645+
46264646
// assets require a valid uuid v4 id
46274647
if (!BaseCoin.isValidUuidV4(options.id)) {
46284648
throw new InvalidIdError(options.name, options.id);

modules/statics/src/coins/ofcCoins.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,7 +1644,7 @@ export const ofcCoins = [
16441644
'SPCX goStock',
16451645
6,
16461646
UnderlyingAsset['sol:gospcx'],
1647-
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN]
1647+
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN, CoinFeature.TOKENIZED_EQUITY, CoinFeature.BITGO_TOKENIZED_EQUITY]
16481648
),
16491649
ofcsolToken(
16501650
'e343b3c2-dcbb-4a9f-a60e-3dd79825c5fb',
@@ -2034,15 +2034,15 @@ export const ofcCoins = [
20342034
'Test SPCX goStock',
20352035
6,
20362036
UnderlyingAsset['tsol:stggospcx'],
2037-
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN]
2037+
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN, CoinFeature.TOKENIZED_EQUITY, CoinFeature.BITGO_TOKENIZED_EQUITY]
20382038
),
20392039
tofcsolToken(
20402040
'84f901ed-5654-47a7-9d08-7ff7c27a8c16',
20412041
'ofctsol:gospcx',
20422042
'Test SPCX goStock',
20432043
6,
20442044
UnderlyingAsset['tsol:gospcx'],
2045-
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN]
2045+
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN, CoinFeature.TOKENIZED_EQUITY, CoinFeature.BITGO_TOKENIZED_EQUITY]
20462046
),
20472047
tofcsolToken(
20482048
'750f0e40-c5b9-464f-874f-dc455cf1494b',

modules/statics/src/coins/solTokens.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3686,7 +3686,7 @@ export const solTokens = [
36863686
'98iaHRfvCnaihEHcLK5EpwLq3w5yHiKKfSgpYLcxEScB',
36873687
'98iaHRfvCnaihEHcLK5EpwLq3w5yHiKKfSgpYLcxEScB',
36883688
UnderlyingAsset['tsol:stggospcx'],
3689-
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN],
3689+
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN, CoinFeature.TOKENIZED_EQUITY, CoinFeature.BITGO_TOKENIZED_EQUITY],
36903690
ProgramID.Token2022ProgramId
36913691
),
36923692
tsolToken(
@@ -3697,7 +3697,7 @@ export const solTokens = [
36973697
'4gvEw3Lx2gkAByv4X8hLnHujneN5bwtMGSu2ZDoip4vj',
36983698
'4gvEw3Lx2gkAByv4X8hLnHujneN5bwtMGSu2ZDoip4vj',
36993699
UnderlyingAsset['tsol:gospcx'],
3700-
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN],
3700+
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN, CoinFeature.TOKENIZED_EQUITY, CoinFeature.BITGO_TOKENIZED_EQUITY],
37013701
ProgramID.Token2022ProgramId
37023702
),
37033703
solToken(
@@ -3708,7 +3708,7 @@ export const solTokens = [
37083708
'AAVvaNDwkGfxGNaf1HJ5JzfwDb1PYmAgXSixRsczyrk4',
37093709
'AAVvaNDwkGfxGNaf1HJ5JzfwDb1PYmAgXSixRsczyrk4',
37103710
UnderlyingAsset['sol:gospcx'],
3711-
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN],
3711+
[...SOL_TOKEN_FEATURES, CoinFeature.STABLECOIN, CoinFeature.TOKENIZED_EQUITY, CoinFeature.BITGO_TOKENIZED_EQUITY],
37123712
ProgramID.Token2022ProgramId
37133713
),
37143714
tsolToken(

modules/statics/test/unit/base.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { CoinFamily } from '../../src';
1+
import { CoinFamily, CoinFeature, coins } from '../../src';
22

33
const should = require('should');
44
const { UnderlyingAsset } = require('../../src/base');
5+
const { solToken, ProgramID } = require('../../src/account');
56

67
describe('UnderlyingAsset', function () {
78
it('UnderlyingAsset values should be unique', function () {
@@ -36,3 +37,88 @@ describe('zkSync Era Base Types', function () {
3637
UnderlyingAsset.ZKSYNCERA.should.equal('zksyncera');
3738
});
3839
});
40+
41+
describe('Tokenized Equity CoinFeatures', function () {
42+
it('TOKENIZED_EQUITY feature value should be tokenized-equity', function () {
43+
CoinFeature.TOKENIZED_EQUITY.should.equal('tokenized-equity');
44+
});
45+
46+
it('BITGO_TOKENIZED_EQUITY feature value should be bitgo-tokenized-equity', function () {
47+
CoinFeature.BITGO_TOKENIZED_EQUITY.should.equal('bitgo-tokenized-equity');
48+
});
49+
50+
it('sol:gospcx should have both TOKENIZED_EQUITY and BITGO_TOKENIZED_EQUITY', function () {
51+
const coin = coins.get('sol:gospcx');
52+
coin.features.should.containEql(CoinFeature.TOKENIZED_EQUITY);
53+
coin.features.should.containEql(CoinFeature.BITGO_TOKENIZED_EQUITY);
54+
});
55+
56+
it('tsol:gospcx should have both TOKENIZED_EQUITY and BITGO_TOKENIZED_EQUITY', function () {
57+
const coin = coins.get('tsol:gospcx');
58+
coin.features.should.containEql(CoinFeature.TOKENIZED_EQUITY);
59+
coin.features.should.containEql(CoinFeature.BITGO_TOKENIZED_EQUITY);
60+
});
61+
62+
it('tsol:stggospcx should have both TOKENIZED_EQUITY and BITGO_TOKENIZED_EQUITY', function () {
63+
const coin = coins.get('tsol:stggospcx');
64+
coin.features.should.containEql(CoinFeature.TOKENIZED_EQUITY);
65+
coin.features.should.containEql(CoinFeature.BITGO_TOKENIZED_EQUITY);
66+
});
67+
68+
it('ofcsol:gospcx should have both TOKENIZED_EQUITY and BITGO_TOKENIZED_EQUITY', function () {
69+
const coin = coins.get('ofcsol:gospcx');
70+
coin.features.should.containEql(CoinFeature.TOKENIZED_EQUITY);
71+
coin.features.should.containEql(CoinFeature.BITGO_TOKENIZED_EQUITY);
72+
});
73+
74+
it('ofctsol:gospcx should have both TOKENIZED_EQUITY and BITGO_TOKENIZED_EQUITY', function () {
75+
const coin = coins.get('ofctsol:gospcx');
76+
coin.features.should.containEql(CoinFeature.TOKENIZED_EQUITY);
77+
coin.features.should.containEql(CoinFeature.BITGO_TOKENIZED_EQUITY);
78+
});
79+
80+
it('ofctsol:stggospcx should have both TOKENIZED_EQUITY and BITGO_TOKENIZED_EQUITY', function () {
81+
const coin = coins.get('ofctsol:stggospcx');
82+
coin.features.should.containEql(CoinFeature.TOKENIZED_EQUITY);
83+
coin.features.should.containEql(CoinFeature.BITGO_TOKENIZED_EQUITY);
84+
});
85+
86+
it('invariant: BITGO_TOKENIZED_EQUITY without TOKENIZED_EQUITY should throw MissingRequiredCoinFeatureError', function () {
87+
let threw = false;
88+
let errorMessage = '';
89+
let errorType = '';
90+
try {
91+
solToken(
92+
'00000000-0000-0000-0000-000000000001',
93+
'test:invalidgostock',
94+
'Invalid goStock',
95+
6,
96+
'AAVvaNDwkGfxGNaf1HJ5JzfwDb1PYmAgXSixRsczyrk4',
97+
'AAVvaNDwkGfxGNaf1HJ5JzfwDb1PYmAgXSixRsczyrk4',
98+
UnderlyingAsset['sol:gospcx'],
99+
[
100+
CoinFeature.ACCOUNT_MODEL,
101+
CoinFeature.REQUIRES_BIG_NUMBER,
102+
CoinFeature.VALUELESS_TRANSFER,
103+
CoinFeature.TRANSACTION_DATA,
104+
CoinFeature.CUSTODY,
105+
CoinFeature.CUSTODY_BITGO_TRUST,
106+
CoinFeature.TSS,
107+
CoinFeature.TSS_COLD,
108+
CoinFeature.BULK_TRANSACTION,
109+
CoinFeature.BITGO_TOKENIZED_EQUITY,
110+
],
111+
ProgramID.Token2022ProgramId
112+
);
113+
} catch (err: unknown) {
114+
threw = true;
115+
if (err instanceof Error) {
116+
errorMessage = err.message;
117+
errorType = err.constructor.name;
118+
}
119+
}
120+
threw.should.be.true();
121+
errorType.should.equal('MissingRequiredCoinFeatureError');
122+
errorMessage.should.containEql('tokenized-equity');
123+
});
124+
});

0 commit comments

Comments
 (0)