Skip to content

feat(sdk-coin-near): add Nep-141 skeleton #6152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/sdk-coin-near/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './lib';
export * from './near';
export * from './tnear';
export * from './register';
export * from './nep141Token';
58 changes: 58 additions & 0 deletions modules/sdk-coin-near/src/nep141Token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Near } from './near';
import { coins, Nep141TokenConfig, NetworkType, tokens } from '@bitgo/statics';
import { BitGoBase, CoinConstructor, NamedCoinConstructor } from '@bitgo/sdk-core';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: external imports should be on top

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, will handle it in #6154


export class Nep141Token extends Near {
public readonly tokenConfig: Nep141TokenConfig;

constructor(bitgo: BitGoBase, tokenConfig: Nep141TokenConfig) {
const staticsCoin = tokenConfig.network === NetworkType.MAINNET ? coins.get('near') : coins.get('tnear');
super(bitgo, staticsCoin);
this.tokenConfig = tokenConfig;
}

static createTokenConstructor(config: Nep141TokenConfig): CoinConstructor {
return (bitgo: BitGoBase) => new Nep141Token(bitgo, config);
}

static createTokenConstructors(): NamedCoinConstructor[] {
const tokensCtors: NamedCoinConstructor[] = [];
for (const token of [...tokens.bitcoin.near.tokens, ...tokens.testnet.near.tokens]) {
const tokenConstructor = Nep141Token.createTokenConstructor(token);
tokensCtors.push({ name: token.type, coinConstructor: tokenConstructor });
}
return tokensCtors;
}

get name(): string {
return this.tokenConfig.name;
}

get coin(): string {
return this.tokenConfig.coin;
}

get contractAddress(): string {
return this.tokenConfig.contractAddress;
}

get decimalPlaces(): number {
return this.tokenConfig.decimalPlaces;
}

getChain(): string {
return this.tokenConfig.type;
}

getBaseChain(): string {
return this.coin;
}

getFullName(): string {
return 'Nep141 Token';
}

getBaseFactor(): number {
return Math.pow(10, this.tokenConfig.decimalPlaces);
}
}
4 changes: 4 additions & 0 deletions modules/sdk-coin-near/src/register.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { BitGoBase } from '@bitgo/sdk-core';
import { Near } from './near';
import { TNear } from './tnear';
import { Nep141Token } from './nep141Token';

export const register = (sdk: BitGoBase): void => {
sdk.register('near', Near.createInstance);
sdk.register('tnear', TNear.createInstance);
Nep141Token.createTokenConstructors().forEach(({ name, coinConstructor }) => {
sdk.register(name, coinConstructor);
});
};
95 changes: 95 additions & 0 deletions modules/statics/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ export interface Sip10TokenConstructorOptions extends AccountConstructorOptions
assetId: string;
}

export interface Nep141TokenConstructorOptions extends AccountConstructorOptions {
contractAddress: string;
}

export interface ContractAddress extends String {
__contractaddress_phantom__: never;
}
Expand Down Expand Up @@ -578,6 +582,21 @@ export class Sip10Token extends AccountCoinToken {
}
}

/**
* The Near network supports tokens
* Near tokens work similar to native near coin
*/
export class Nep141Token extends AccountCoinToken {
public contractAddress: string;
constructor(options: Nep141TokenConstructorOptions) {
super({
...options,
});

this.contractAddress = options.contractAddress;
}
}

/**
* Factory function for account coin instances.
*
Expand Down Expand Up @@ -2890,3 +2909,79 @@ export function tsip10Token(
) {
return sip10Token(id, name, fullName, decimalPlaces, assetId, asset, features, prefix, suffix, network);
}

/**
* Factory function for nep141 token instances.
*
* @param id uuid v4
* @param name unique identifier of the token
* @param fullName Complete human-readable name of the token
* @param decimalPlaces Number of decimal places this token supports (divisibility exponent)
* @param contractAddress Contract address of this token
* @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 prefix Optional token prefix. Defaults to empty string
* @param suffix Optional token suffix. Defaults to token name.
* @param network Optional token network. Defaults to Near main network.
* @param primaryKeyCurve The elliptic curve for this chain/token
*/
export function nep141Token(
id: string,
name: string,
fullName: string,
decimalPlaces: number,
contractAddress: string,
asset: UnderlyingAsset,
features: CoinFeature[] = AccountCoin.DEFAULT_FEATURES,
prefix = '',
suffix: string = name.toUpperCase(),
network: AccountNetwork = Networks.main.near,
primaryKeyCurve: KeyCurve = KeyCurve.Ed25519
) {
return Object.freeze(
new Nep141Token({
id,
name,
fullName,
network,
decimalPlaces,
contractAddress,
prefix,
suffix,
features,
asset,
isToken: true,
primaryKeyCurve,
baseUnit: BaseUnit.NEAR,
})
);
}

/**
* Factory function for testnet nep141 token instances.
*
* @param id uuid v4
* @param name unique identifier of the token
* @param fullName Complete human-readable name of the token
* @param decimalPlaces Number of decimal places this token supports (divisibility exponent)
* @param contractAddress Contract address of this token
* @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 prefix Optional token prefix. Defaults to empty string
* @param suffix Optional token suffix. Defaults to token name.
* @param network Optional token network. Defaults to the testnet Near network.
*/
export function tnep141Token(
id: string,
name: string,
fullName: string,
decimalPlaces: number,
contractAddress: string,
asset: UnderlyingAsset,
features: CoinFeature[] = AccountCoin.DEFAULT_FEATURES,
prefix = '',
suffix: string = name.toUpperCase(),
network: AccountNetwork = Networks.test.near
) {
return nep141Token(id, name, fullName, decimalPlaces, contractAddress, asset, features, prefix, suffix, network);
}
1 change: 1 addition & 0 deletions modules/statics/src/coinFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ export const NEAR_FEATURES = [
CoinFeature.STAKING,
CoinFeature.REBUILD_ON_CUSTODY_SIGNING,
CoinFeature.CUSTODY_BITGO_FRANKFURT,
CoinFeature.SUPPORTS_TOKENS,
];
export const MATIC_FEATURES = [
...AccountCoin.DEFAULT_FEATURES,
Expand Down
1 change: 1 addition & 0 deletions modules/statics/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export {
AptCoin,
AptNFTCollection,
Sip10Token,
Nep141Token,
} from './account';
export { CoinMap } from './map';
export { gatekeep } from './gatekeep';
Expand Down
32 changes: 32 additions & 0 deletions modules/statics/src/tokenConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Erc20Coin,
Erc721Coin,
HederaToken,
Nep141Token,
OpethERC20Token,
PolygonERC20Token,
Sip10Token,
Expand Down Expand Up @@ -106,6 +107,10 @@ export type Sip10TokenConfig = BaseNetworkConfig & {
assetId: string;
};

export type Nep141TokenConfig = BaseNetworkConfig & {
contractAddress: string;
};

export interface Tokens {
bitcoin: {
eth: {
Expand Down Expand Up @@ -178,6 +183,9 @@ export interface Tokens {
stx: {
tokens: Sip10TokenConfig[];
};
near: {
tokens: Nep141TokenConfig[];
};
};
testnet: {
eth: {
Expand Down Expand Up @@ -250,6 +258,9 @@ export interface Tokens {
stx: {
tokens: Sip10TokenConfig[];
};
near: {
tokens: Nep141TokenConfig[];
};
};
}

Expand Down Expand Up @@ -698,6 +709,21 @@ const getFormattedSip10Tokens = (customCoinMap = coins) =>
return acc;
}, []);

const getFormattedNep141Tokens = (customCoinMap = coins) =>
customCoinMap.reduce((acc: Nep141TokenConfig[], coin) => {
if (coin instanceof Nep141Token) {
acc.push({
type: coin.name,
coin: coin.network.type === NetworkType.MAINNET ? 'near' : 'tnear',
network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
name: coin.fullName,
contractAddress: coin.contractAddress,
decimalPlaces: coin.decimalPlaces,
});
}
return acc;
}, []);

export const getFormattedTokens = (coinMap = coins): Tokens => {
const formattedAptNFTCollections = getFormattedAptNFTCollections(coinMap);
return {
Expand Down Expand Up @@ -776,6 +802,9 @@ export const getFormattedTokens = (coinMap = coins): Tokens => {
stx: {
tokens: getFormattedSip10Tokens(coinMap).filter((token) => token.network === 'Mainnet'),
},
near: {
tokens: getFormattedNep141Tokens(coinMap).filter((token) => token.network === 'Mainnet'),
},
},
testnet: {
eth: {
Expand Down Expand Up @@ -852,6 +881,9 @@ export const getFormattedTokens = (coinMap = coins): Tokens => {
coredao: {
tokens: getFormattedCoredaoTokens(coinMap).filter((token) => token.network === 'Testnet'),
},
near: {
tokens: getFormattedNep141Tokens(coinMap).filter((token) => token.network === 'Testnet'),
},
},
};
};
Expand Down