status-go/api/defaults.go

1041 lines
18 KiB
Go
Raw Normal View History

2023-03-16 14:49:25 +00:00
package api
import (
"crypto/rand"
"encoding/json"
"math/big"
2024-03-28 15:01:44 +00:00
"path/filepath"
2023-03-16 14:49:25 +00:00
"github.com/google/uuid"
"github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol"
2023-03-16 14:49:25 +00:00
"github.com/status-im/status-go/protocol/identity/alias"
"github.com/status-im/status-go/protocol/protobuf"
2023-03-16 14:49:25 +00:00
"github.com/status-im/status-go/protocol/requests"
)
const pathWalletRoot = "m/44'/60'/0'/0"
const pathEIP1581 = "m/43'/60'/1581'"
const pathDefaultChat = pathEIP1581 + "/0'/0"
const pathEncryption = pathEIP1581 + "/1'/0"
2023-03-16 14:49:25 +00:00
const pathDefaultWallet = pathWalletRoot + "/0"
2023-03-16 16:45:24 +00:00
const defaultMnemonicLength = 12
2023-12-07 13:45:53 +00:00
const shardsTestClusterID = 16
const walletAccountDefaultName = "Account 1"
const keystoreRelativePath = "keystore"
const DefaultKeycardPairingDataFile = "/ethereum/mainnet_rpc/keycard/pairings.json"
2023-03-16 14:49:25 +00:00
const DefaultDataDir = "/ethereum/mainnet_rpc"
const DefaultNodeName = "StatusIM"
const DefaultLogFile = "geth.log"
const DefaultLogLevel = "ERROR"
const DefaultMaxPeers = 20
const DefaultMaxPendingPeers = 20
const DefaultListenAddr = ":0"
const DefaultMaxMessageDeliveryAttempts = 3
const DefaultVerifyTransactionChainID = 1
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet, pathEncryption}
2023-03-16 14:49:25 +00:00
var DefaultFleet = params.FleetStatusProd
2023-12-07 13:45:53 +00:00
var overrideApiConfig = overrideApiConfigProd
func defaultSettings(keyUID string, address string, derivedAddresses map[string]generator.AccountInfo) (*settings.Settings, error) {
2023-03-16 14:49:25 +00:00
chatKeyString := derivedAddresses[pathDefaultChat].PublicKey
s := &settings.Settings{}
s.BackupEnabled = true
logLevel := "INFO"
s.LogLevel = &logLevel
s.ProfilePicturesShowTo = settings.ProfilePicturesShowToEveryone
s.ProfilePicturesVisibility = settings.ProfilePicturesVisibilityEveryone
s.KeyUID = keyUID
s.Address = types.HexToAddress(address)
s.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address)
2023-10-13 14:31:56 +00:00
s.URLUnfurlingMode = settings.URLUnfurlingAlwaysAsk
2023-03-16 14:49:25 +00:00
// Set chat key & name
name, err := alias.GenerateFromPublicKeyString(chatKeyString)
if err != nil {
return nil, err
}
s.Name = name
s.PublicKey = chatKeyString
2023-03-16 14:49:25 +00:00
s.DappsAddress = types.HexToAddress(derivedAddresses[pathDefaultWallet].Address)
s.EIP1581Address = types.HexToAddress(derivedAddresses[pathEIP1581].Address)
2023-03-16 14:49:25 +00:00
signingPhrase, err := buildSigningPhrase()
if err != nil {
return nil, err
}
s.SigningPhrase = signingPhrase
2023-03-16 14:49:25 +00:00
s.SendPushNotifications = true
s.InstallationID = uuid.New().String()
s.UseMailservers = true
2023-03-16 14:49:25 +00:00
s.PreviewPrivacy = true
s.PeerSyncingEnabled = false
s.Currency = "usd"
s.LinkPreviewRequestEnabled = true
2023-03-16 14:49:25 +00:00
visibleTokens := make(map[string][]string)
visibleTokens["mainnet"] = []string{"SNT"}
visibleTokensJSON, err := json.Marshal(visibleTokens)
if err != nil {
return nil, err
}
visibleTokenJSONRaw := json.RawMessage(visibleTokensJSON)
s.WalletVisibleTokens = &visibleTokenJSONRaw
2023-03-16 14:49:25 +00:00
// TODO: fix this
networks := make([]map[string]string, 0)
networksJSON, err := json.Marshal(networks)
if err != nil {
return nil, err
}
networkRawMessage := json.RawMessage(networksJSON)
s.Networks = &networkRawMessage
s.CurrentNetwork = "mainnet_rpc"
2023-03-16 14:49:25 +00:00
s.TokenGroupByCommunity = false
s.ShowCommunityAssetWhenSendingTokens = true
s.DisplayAssetsBelowBalance = false
s.TestNetworksEnabled = false
// Default user status
currentUserStatus, err := json.Marshal(protocol.UserStatus{
PublicKey: chatKeyString,
StatusType: int(protobuf.StatusUpdate_AUTOMATIC),
Clock: 0,
CustomText: "",
})
if err != nil {
return nil, err
}
userRawMessage := json.RawMessage(currentUserStatus)
s.CurrentUserStatus = &userRawMessage
return s, nil
2023-03-16 14:49:25 +00:00
}
2023-09-27 17:26:10 +00:00
func SetDefaultFleet(nodeConfig *params.NodeConfig) error {
2023-12-07 13:45:53 +00:00
return SetFleet(DefaultFleet, nodeConfig)
2023-11-10 15:12:54 +00:00
}
func SetFleet(fleet string, nodeConfig *params.NodeConfig) error {
specifiedWakuV2Config := nodeConfig.WakuV2Config
2023-12-07 13:45:53 +00:00
nodeConfig.WakuV2Config = params.WakuV2Config{
Enabled: true,
EnableDiscV5: true,
DiscoveryLimit: 20,
Host: "0.0.0.0",
AutoUpdate: true,
// mobile may need override following options
LightClient: specifiedWakuV2Config.LightClient,
EnableStoreConfirmationForMessagesSent: specifiedWakuV2Config.EnableStoreConfirmationForMessagesSent,
EnableMissingMessageVerification: specifiedWakuV2Config.EnableMissingMessageVerification,
Nameserver: specifiedWakuV2Config.Nameserver,
2023-12-07 13:45:53 +00:00
}
2023-11-10 15:12:54 +00:00
clusterConfig, err := params.LoadClusterConfigFromFleet(fleet)
2023-09-27 17:26:10 +00:00
if err != nil {
return err
}
nodeConfig.ClusterConfig = *clusterConfig
2023-12-07 13:45:53 +00:00
nodeConfig.ClusterConfig.Fleet = fleet
nodeConfig.ClusterConfig.WakuNodes = params.DefaultWakuNodes(fleet)
nodeConfig.ClusterConfig.DiscV5BootstrapNodes = params.DefaultDiscV5Nodes(fleet)
2023-12-07 13:45:53 +00:00
if fleet == params.FleetStatusProd {
2023-12-07 13:45:53 +00:00
nodeConfig.ClusterConfig.ClusterID = shardsTestClusterID
}
2023-09-27 17:26:10 +00:00
return nil
}
func buildWalletConfig(request *requests.WalletSecretsConfig, statusProxyEnabled bool) params.WalletConfig {
walletConfig := params.WalletConfig{
Enabled: true,
AlchemyAPIKeys: make(map[uint64]string),
}
if request.OpenseaAPIKey != "" {
walletConfig.OpenseaAPIKey = request.OpenseaAPIKey
}
2023-10-12 19:40:13 +00:00
if request.RaribleMainnetAPIKey != "" {
walletConfig.RaribleMainnetAPIKey = request.RaribleMainnetAPIKey
2023-10-12 19:40:13 +00:00
}
if request.RaribleTestnetAPIKey != "" {
walletConfig.RaribleTestnetAPIKey = request.RaribleTestnetAPIKey
2023-10-12 19:40:13 +00:00
}
if request.InfuraToken != "" {
walletConfig.InfuraAPIKey = request.InfuraToken
}
if request.InfuraSecret != "" {
walletConfig.InfuraAPIKeySecret = request.InfuraSecret
}
if request.AlchemyEthereumMainnetToken != "" {
walletConfig.AlchemyAPIKeys[mainnetChainID] = request.AlchemyEthereumMainnetToken
}
if request.AlchemyEthereumGoerliToken != "" {
walletConfig.AlchemyAPIKeys[goerliChainID] = request.AlchemyEthereumGoerliToken
}
2023-11-16 20:46:01 +00:00
if request.AlchemyEthereumSepoliaToken != "" {
walletConfig.AlchemyAPIKeys[sepoliaChainID] = request.AlchemyEthereumSepoliaToken
2023-11-16 20:46:01 +00:00
}
if request.AlchemyArbitrumMainnetToken != "" {
walletConfig.AlchemyAPIKeys[arbitrumChainID] = request.AlchemyArbitrumMainnetToken
}
if request.AlchemyArbitrumGoerliToken != "" {
walletConfig.AlchemyAPIKeys[arbitrumGoerliChainID] = request.AlchemyArbitrumGoerliToken
}
2023-11-16 20:46:01 +00:00
if request.AlchemyArbitrumSepoliaToken != "" {
walletConfig.AlchemyAPIKeys[arbitrumSepoliaChainID] = request.AlchemyArbitrumSepoliaToken
2023-11-16 20:46:01 +00:00
}
if request.AlchemyOptimismMainnetToken != "" {
walletConfig.AlchemyAPIKeys[optimismChainID] = request.AlchemyOptimismMainnetToken
}
if request.AlchemyOptimismGoerliToken != "" {
walletConfig.AlchemyAPIKeys[optimismGoerliChainID] = request.AlchemyOptimismGoerliToken
}
2023-11-16 20:46:01 +00:00
if request.AlchemyOptimismSepoliaToken != "" {
walletConfig.AlchemyAPIKeys[optimismSepoliaChainID] = request.AlchemyOptimismSepoliaToken
2023-11-16 20:46:01 +00:00
}
if request.StatusProxyMarketUser != "" {
walletConfig.StatusProxyMarketUser = request.StatusProxyMarketUser
}
if request.StatusProxyMarketPassword != "" {
walletConfig.StatusProxyMarketPassword = request.StatusProxyMarketPassword
}
if request.StatusProxyBlockchainUser != "" {
walletConfig.StatusProxyBlockchainUser = request.StatusProxyBlockchainUser
}
if request.StatusProxyBlockchainPassword != "" {
walletConfig.StatusProxyBlockchainPassword = request.StatusProxyBlockchainPassword
}
walletConfig.StatusProxyEnabled = statusProxyEnabled
return walletConfig
}
func overrideApiConfigProd(nodeConfig *params.NodeConfig, config *requests.APIConfig) {
nodeConfig.APIModules = config.APIModules
nodeConfig.ConnectorConfig.Enabled = config.ConnectorEnabled
nodeConfig.HTTPEnabled = config.HTTPEnabled
nodeConfig.HTTPHost = config.HTTPHost
nodeConfig.HTTPPort = config.HTTPPort
nodeConfig.HTTPVirtualHosts = config.HTTPVirtualHosts
nodeConfig.WSEnabled = config.WSEnabled
nodeConfig.WSHost = config.WSHost
nodeConfig.WSPort = config.WSPort
}
2024-03-22 10:55:09 +00:00
func defaultNodeConfig(installationID string, request *requests.CreateAccount, opts ...params.Option) (*params.NodeConfig, error) {
// Set mainnet
nodeConfig := &params.NodeConfig{}
nodeConfig.LogEnabled = request.LogEnabled
nodeConfig.LogFile = DefaultLogFile
nodeConfig.LogDir = request.LogFilePath
nodeConfig.LogLevel = DefaultLogLevel
nodeConfig.DataDir = DefaultDataDir
2024-03-28 15:01:44 +00:00
nodeConfig.ProcessBackedupMessages = false
nodeConfig.KeycardPairingDataFile = DefaultKeycardPairingDataFile
if request.KeycardPairingDataFile != nil {
nodeConfig.KeycardPairingDataFile = *request.KeycardPairingDataFile
}
if request.LogLevel != nil {
nodeConfig.LogLevel = *request.LogLevel
2024-03-28 15:01:44 +00:00
nodeConfig.LogEnabled = true
} else {
nodeConfig.LogEnabled = false
}
if request.TestOverrideNetworks != nil {
nodeConfig.Networks = request.TestOverrideNetworks
} else {
nodeConfig.Networks = BuildDefaultNetworks(&request.WalletSecretsConfig)
}
2024-03-28 15:01:44 +00:00
if request.NetworkID != nil {
nodeConfig.NetworkID = *request.NetworkID
} else {
nodeConfig.NetworkID = nodeConfig.Networks[0].ChainID
}
if request.UpstreamConfig != "" {
nodeConfig.UpstreamConfig = params.UpstreamRPCConfig{
Enabled: true,
URL: request.UpstreamConfig,
}
2024-03-28 15:01:44 +00:00
} else {
nodeConfig.UpstreamConfig.URL = defaultNetworks[0].RPCURL
nodeConfig.UpstreamConfig.Enabled = true
}
nodeConfig.Name = DefaultNodeName
nodeConfig.Rendezvous = false
nodeConfig.NoDiscovery = true
nodeConfig.MaxPeers = DefaultMaxPeers
nodeConfig.MaxPendingPeers = DefaultMaxPendingPeers
nodeConfig.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig, request.StatusProxyEnabled)
2023-03-16 14:49:25 +00:00
nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true}
nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: true}
nodeConfig.PermissionsConfig = params.PermissionsConfig{Enabled: true}
nodeConfig.MailserversConfig = params.MailserversConfig{Enabled: true}
nodeConfig.ListenAddr = DefaultListenAddr
2023-12-07 13:45:53 +00:00
fleet := request.WakuV2Fleet
if fleet == "" {
fleet = DefaultFleet
}
err := SetFleet(fleet, nodeConfig)
2023-09-27 17:26:10 +00:00
if err != nil {
return nil, err
}
2023-07-03 12:48:21 +00:00
if request.WakuV2LightClient {
nodeConfig.WakuV2Config.LightClient = true
}
if request.WakuV2EnableMissingMessageVerification {
nodeConfig.WakuV2Config.EnableMissingMessageVerification = true
2023-07-03 12:48:21 +00:00
}
if request.WakuV2EnableStoreConfirmationForMessagesSent {
nodeConfig.WakuV2Config.EnableStoreConfirmationForMessagesSent = true
}
2023-03-16 14:49:25 +00:00
if request.WakuV2Nameserver != nil {
nodeConfig.WakuV2Config.Nameserver = *request.WakuV2Nameserver
}
if request.TelemetryServerURL != "" {
nodeConfig.WakuV2Config.TelemetryServerURL = request.TelemetryServerURL
}
2023-03-16 14:49:25 +00:00
nodeConfig.ShhextConfig = params.ShhextConfig{
InstallationID: installationID,
MaxMessageDeliveryAttempts: DefaultMaxMessageDeliveryAttempts,
2023-03-16 14:49:25 +00:00
MailServerConfirmations: true,
VerifyTransactionChainID: DefaultVerifyTransactionChainID,
2023-03-16 14:49:25 +00:00
DataSyncEnabled: true,
PFSEnabled: true,
}
if request.VerifyTransactionURL != nil {
nodeConfig.ShhextConfig.VerifyTransactionURL = *request.VerifyTransactionURL
2024-03-28 15:01:44 +00:00
} else {
nodeConfig.ShhextConfig.VerifyTransactionURL = defaultNetworks[0].FallbackURL
2023-03-16 14:49:25 +00:00
}
if request.VerifyENSURL != nil {
nodeConfig.ShhextConfig.VerifyENSURL = *request.VerifyENSURL
2024-03-28 15:01:44 +00:00
} else {
nodeConfig.ShhextConfig.VerifyENSURL = defaultNetworks[0].FallbackURL
2023-03-16 14:49:25 +00:00
}
if request.VerifyTransactionChainID != nil {
nodeConfig.ShhextConfig.VerifyTransactionChainID = *request.VerifyTransactionChainID
}
if request.VerifyENSContractAddress != nil {
nodeConfig.ShhextConfig.VerifyENSContractAddress = *request.VerifyENSContractAddress
}
2024-03-28 15:01:44 +00:00
if request.NetworkID != nil {
nodeConfig.NetworkID = *request.NetworkID
}
nodeConfig.TorrentConfig = params.TorrentConfig{
Enabled: false,
Port: 0,
DataDir: filepath.Join(nodeConfig.RootDataDir, params.ArchivesRelativePath),
TorrentDir: filepath.Join(nodeConfig.RootDataDir, params.TorrentTorrentsRelativePath),
2024-03-28 15:01:44 +00:00
}
if request.TorrentConfigEnabled != nil {
nodeConfig.TorrentConfig.Enabled = *request.TorrentConfigEnabled
}
if request.TorrentConfigPort != nil {
nodeConfig.TorrentConfig.Port = *request.TorrentConfigPort
}
if request.APIConfig != nil {
overrideApiConfig(nodeConfig, request.APIConfig)
}
2024-03-22 10:55:09 +00:00
for _, opt := range opts {
if err := opt(nodeConfig); err != nil {
return nil, err
}
}
2023-03-16 14:49:25 +00:00
return nodeConfig, nil
}
func buildSigningPhrase() (string, error) {
length := big.NewInt(int64(len(dictionary)))
a, err := rand.Int(rand.Reader, length)
if err != nil {
return "", err
}
b, err := rand.Int(rand.Reader, length)
if err != nil {
return "", err
}
c, err := rand.Int(rand.Reader, length)
if err != nil {
return "", err
}
return dictionary[a.Int64()] + " " + dictionary[b.Int64()] + " " + dictionary[c.Int64()], nil
}
var dictionary = []string{
"acid",
"alto",
"apse",
"arch",
"area",
"army",
"atom",
"aunt",
"babe",
"baby",
"back",
"bail",
"bait",
"bake",
"ball",
"band",
"bank",
"barn",
"base",
"bass",
"bath",
"bead",
"beak",
"beam",
"bean",
"bear",
"beat",
"beef",
"beer",
"beet",
"bell",
"belt",
"bend",
"bike",
"bill",
"bird",
"bite",
"blow",
"blue",
"boar",
"boat",
"body",
"bolt",
"bomb",
"bone",
"book",
"boot",
"bore",
"boss",
"bowl",
"brow",
"bulb",
"bull",
"burn",
"bush",
"bust",
"cafe",
"cake",
"calf",
"call",
"calm",
"camp",
"cane",
"cape",
"card",
"care",
"carp",
"cart",
"case",
"cash",
"cast",
"cave",
"cell",
"cent",
"chap",
"chef",
"chin",
"chip",
"chop",
"chub",
"chug",
"city",
"clam",
"clef",
"clip",
"club",
"clue",
"coal",
"coat",
"code",
"coil",
"coin",
"coke",
"cold",
"colt",
"comb",
"cone",
"cook",
"cope",
"copy",
"cord",
"cork",
"corn",
"cost",
"crab",
"craw",
"crew",
"crib",
"crop",
"crow",
"curl",
"cyst",
"dame",
"dare",
"dark",
"dart",
"dash",
"data",
"date",
"dead",
"deal",
"dear",
"debt",
"deck",
"deep",
"deer",
"desk",
"dhow",
"diet",
"dill",
"dime",
"dirt",
"dish",
"disk",
"dock",
"doll",
"door",
"dory",
"drag",
"draw",
"drop",
"drug",
"drum",
"duck",
"dump",
"dust",
"duty",
"ease",
"east",
"eave",
"eddy",
"edge",
"envy",
"epee",
"exam",
"exit",
"face",
"fact",
"fail",
"fall",
"fame",
"fang",
"farm",
"fawn",
"fear",
"feed",
"feel",
"feet",
"file",
"fill",
"film",
"find",
"fine",
"fire",
"fish",
"flag",
"flat",
"flax",
"flow",
"foam",
"fold",
"font",
"food",
"foot",
"fork",
"form",
"fort",
"fowl",
"frog",
"fuel",
"full",
"gain",
"gale",
"galn",
"game",
"garb",
"gate",
"gear",
"gene",
"gift",
"girl",
"give",
"glad",
"glen",
"glue",
"glut",
"goal",
"goat",
"gold",
"golf",
"gong",
"good",
"gown",
"grab",
"gram",
"gray",
"grey",
"grip",
"grit",
"gyro",
"hail",
"hair",
"half",
"hall",
"hand",
"hang",
"harm",
"harp",
"hate",
"hawk",
"head",
"heat",
"heel",
"hell",
"helo",
"help",
"hemp",
"herb",
"hide",
"high",
"hill",
"hire",
"hive",
"hold",
"hole",
"home",
"hood",
"hoof",
"hook",
"hope",
"hops",
"horn",
"hose",
"host",
"hour",
"hunt",
"hurt",
"icon",
"idea",
"inch",
"iris",
"iron",
"item",
"jail",
"jeep",
"jeff",
"joey",
"join",
"joke",
"judo",
"jump",
"junk",
"jury",
"jute",
"kale",
"keep",
"kick",
"kill",
"kilt",
"kind",
"king",
"kiss",
"kite",
"knee",
"knot",
"lace",
"lack",
"lady",
"lake",
"lamb",
"lamp",
"land",
"lark",
"lava",
"lawn",
"lead",
"leaf",
"leek",
"lier",
"life",
"lift",
"lily",
"limo",
"line",
"link",
"lion",
"lisa",
"list",
"load",
"loaf",
"loan",
"lock",
"loft",
"long",
"look",
"loss",
"lout",
"love",
"luck",
"lung",
"lute",
"lynx",
"lyre",
"maid",
"mail",
"main",
"make",
"male",
"mall",
"manx",
"many",
"mare",
"mark",
"mask",
"mass",
"mate",
"math",
"meal",
"meat",
"meet",
"menu",
"mess",
"mice",
"midi",
"mile",
"milk",
"mime",
"mind",
"mine",
"mini",
"mint",
"miss",
"mist",
"moat",
"mode",
"mole",
"mood",
"moon",
"most",
"moth",
"move",
"mule",
"mutt",
"nail",
"name",
"neat",
"neck",
"need",
"neon",
"nest",
"news",
"node",
"nose",
"note",
"oboe",
"okra",
"open",
"oval",
"oven",
"oxen",
"pace",
"pack",
"page",
"pail",
"pain",
"pair",
"palm",
"pard",
"park",
"part",
"pass",
"past",
"path",
"peak",
"pear",
"peen",
"peer",
"pelt",
"perp",
"pest",
"pick",
"pier",
"pike",
"pile",
"pimp",
"pine",
"ping",
"pink",
"pint",
"pipe",
"piss",
"pith",
"plan",
"play",
"plot",
"plow",
"poem",
"poet",
"pole",
"polo",
"pond",
"pony",
"poof",
"pool",
"port",
"post",
"prow",
"pull",
"puma",
"pump",
"pupa",
"push",
"quit",
"race",
"rack",
"raft",
"rage",
"rail",
"rain",
"rake",
"rank",
"rate",
"read",
"rear",
"reef",
"rent",
"rest",
"rice",
"rich",
"ride",
"ring",
"rise",
"risk",
"road",
"robe",
"rock",
"role",
"roll",
"roof",
"room",
"root",
"rope",
"rose",
"ruin",
"rule",
"rush",
"ruth",
"sack",
"safe",
"sage",
"sail",
"sale",
"salt",
"sand",
"sari",
"sash",
"save",
"scow",
"seal",
"seat",
"seed",
"self",
"sell",
"shed",
"shin",
"ship",
"shoe",
"shop",
"shot",
"show",
"sick",
"side",
"sign",
"silk",
"sill",
"silo",
"sing",
"sink",
"site",
"size",
"skin",
"sled",
"slip",
"smog",
"snob",
"snow",
"soap",
"sock",
"soda",
"sofa",
"soft",
"soil",
"song",
"soot",
"sort",
"soup",
"spot",
"spur",
"stag",
"star",
"stay",
"stem",
"step",
"stew",
"stop",
"stud",
"suck",
"suit",
"swan",
"swim",
"tail",
"tale",
"talk",
"tank",
"tard",
"task",
"taxi",
"team",
"tear",
"teen",
"tell",
"temp",
"tent",
"term",
"test",
"text",
"thaw",
"tile",
"till",
"time",
"tire",
"toad",
"toga",
"togs",
"tone",
"tool",
"toot",
"tote",
"tour",
"town",
"tram",
"tray",
"tree",
"trim",
"trip",
"tuba",
"tube",
"tuna",
"tune",
"turn",
"tutu",
"twig",
"type",
"unit",
"user",
"vane",
"vase",
"vast",
"veal",
"veil",
"vein",
"vest",
"vibe",
"view",
"vise",
"wait",
"wake",
"walk",
"wall",
"wash",
"wasp",
"wave",
"wear",
"weed",
"week",
"well",
"west",
"whip",
"wife",
"will",
"wind",
"wine",
"wing",
"wire",
"wish",
"wolf",
"wood",
"wool",
"word",
"work",
"worm",
"wrap",
"wren",
"yard",
"yarn",
"yawl",
"year",
"yoga",
"yoke",
"yurt",
"zinc",
"zone",
}