spb/os-vault Public
Self-custody, multi-chain crypto wallet for macOS. One recovery phrase, six chain families, zero API keys — nothing leaves your Mac.
Swift 96%
Shell 3.4%
Makefile 0.6%
1import Foundation23// MARK: - Deprecation45@available(*, deprecated, renamed: "TokenMetadata")6public typealias Token = TokenMetadata78public extension TokenMetadata {9 @available(*, deprecated, renamed: "mintAddress")10 var address: String {11 mintAddress12 }13}1415// MARK: - TokenMetadata1617/// Common token metadata structure.18public struct TokenMetadata: Hashable, Codable, Equatable {19 public let chainId: Int20 public let mintAddress: String21 public let symbol: String22 public let name: String23 public let decimals: Decimals24 public let logoURI: String?25 public var tags: [TokenTag]26 public let extensions: [String: TokenExtensionValue]?27 public let supply: UInt64?28 public private(set) var isNative = false2930 let _tags: [String]?3132 enum CodingKeys: String, CodingKey {33 case chainId, mintAddress = "address", symbol, name, decimals, logoURI, extensions, _tags = "tags", supply34 }3536 public init(from decoder: Decoder) throws {37 let container = try decoder.container(keyedBy: CodingKeys.self)38 chainId = try container.decode(Int.self, forKey: .chainId)39 mintAddress = try container.decode(String.self, forKey: .mintAddress)40 symbol = try container.decode(String.self, forKey: .symbol)41 name = try container.decode(String.self, forKey: .name)42 decimals = try container.decode(Decimals.self, forKey: .decimals)43 logoURI = try container.decodeIfPresent(String.self, forKey: .logoURI)44 extensions = try container.decodeIfPresent([String: TokenExtensionValue].self, forKey: .extensions)45 _tags = try container.decodeIfPresent([String].self, forKey: ._tags)46 supply = try container.decodeIfPresent(UInt64.self, forKey: .supply)4748 // Customizing tags49 tags = _tags?.map { tag in TokenTag(name: tag, description: tag) } ?? []50 }5152 public init(53 tags: [String]?,54 chainId: Int,55 mintAddress: String,56 symbol: String,57 name: String,58 decimals: UInt8,59 logoURI: String?,60 extensions: [String: TokenExtensionValue]?,61 isNative: Bool = false,62 supply: UInt64? = nil63 ) {64 _tags = tags65 self.chainId = chainId66 self.mintAddress = mintAddress67 self.symbol = symbol68 self.name = name69 self.decimals = decimals70 self.logoURI = logoURI71 self.tags = tags?.map { tag in TokenTag(name: tag, description: tag) } ?? []72 self.extensions = extensions73 self.isNative = isNative74 self.supply = supply75 }7677 public static func unsupported(78 tags: [String]?,79 mint: String,80 decimals: Decimals,81 symbol: String,82 supply: UInt64?83 ) -> TokenMetadata {84 TokenMetadata(85 tags: tags,86 chainId: 101,87 mintAddress: mint,88 symbol: symbol,89 name: mint,90 decimals: decimals,91 logoURI: nil,92 extensions: nil,93 supply: supply94 )95 }9697 @available(*, deprecated, renamed: "isNative")98 public var isNativeSOL: Bool {99 isNative100 }101102 public func hasSameMintAddress(with other: TokenMetadata) -> Bool {103 mintAddress == other.mintAddress104 }105106 public var generalTokenExtensions: GeneralTokenExtension {107 GeneralTokenExtension(data: extensions ?? [:])108 }109}110111public struct TokenTag: Hashable, Codable {112 public var name: String113 public var description: String114}115116public enum TokenExtensionValue: Hashable, Codable {117 case string(String)118 case int(Int)119 case double(Double)120 case bool(Bool)121 case unknown122123 public init(from decoder: Decoder) throws {124 let container = try decoder.singleValueContainer()125126 if let value = try? container.decode(String.self) {127 self = .string(value)128 } else if let value = try? container.decode(Int.self) {129 self = .int(value)130 } else if let value = try? container.decode(Double.self) {131 self = .double(value)132 } else if let value = try? container.decode(Bool.self) {133 self = .bool(value)134 } else {135 self = .unknown136 }137 }138139 public func encode(to encoder: Encoder) throws {140 var container = encoder.singleValueContainer()141142 switch self {143 case let .string(value):144 try container.encode(value)145 case let .int(value):146 try container.encode(value)147 case let .double(value):148 try container.encode(value)149 case let .bool(value):150 try container.encode(value)151 case .unknown:152 return153 }154 }155156 public var stringValue: String? {157 if case let .string(value) = self {158 return value159 } else {160 return nil161 }162 }163164 public var intValue: Int? {165 if case let .int(value) = self {166 return value167 } else {168 return nil169 }170 }171172 public var doubleValue: Double? {173 switch self {174 case let .int(value):175 return Double(value)176 case let .double(value):177 return value178 default:179 return nil180 }181 }182183 public var boolValue: Bool? {184 if case let .bool(value) = self {185 return value186 } else {187 return nil188 }189 }190}191192public struct GeneralTokenExtension: Hashable, Codable {193 public let website: String?194 public let bridgeContract: String?195 public let assetContract: String?196 public let address: String?197 public let explorer: String?198 public let twitter: String?199 public let github: String?200 public let medium: String?201 public let tgann: String?202 public let tggroup: String?203 public let discord: String?204 public let serumV3Usdt: String?205 public let serumV3Usdc: String?206 public let coingeckoId: String?207 public let imageUrl: String?208 public let description: String?209210 public init(data: [String: TokenExtensionValue]) {211 website = data["stringValue"]?.stringValue212 bridgeContract = data["bridgeContract"]?.stringValue213 assetContract = data["assetContract"]?.stringValue214 address = data["address"]?.stringValue215 explorer = data["explorer"]?.stringValue216 twitter = data["twitter"]?.stringValue217 github = data["github"]?.stringValue218 medium = data["medium"]?.stringValue219 tgann = data["tgann"]?.stringValue220 tggroup = data["tggroup"]?.stringValue221 discord = data["discord"]?.stringValue222 serumV3Usdt = data["serumV3Usdt"]?.stringValue223 serumV3Usdc = data["serumV3Usdc"]?.stringValue224 coingeckoId = data["coingeckoId"]?.stringValue225 imageUrl = data["imageUrl"]?.stringValue226 description = data["description"]?.stringValue227 }228229 public init(230 website: String? = nil,231 bridgeContract: String? = nil,232 assetContract: String? = nil,233 address: String? = nil,234 explorer: String? = nil,235 twitter: String? = nil,236 github: String? = nil,237 medium: String? = nil,238 tgann: String? = nil,239 tggroup: String? = nil,240 discord: String? = nil,241 serumV3Usdt: String? = nil,242 serumV3Usdc: String? = nil,243 coingeckoId: String?,244 imageUrl: String? = nil,245 description: String? = nil246 ) {247 self.website = website248 self.bridgeContract = bridgeContract249 self.assetContract = assetContract250 self.address = address251 self.explorer = explorer252 self.twitter = twitter253 self.github = github254 self.medium = medium255 self.tgann = tgann256 self.tggroup = tggroup257 self.discord = discord258 self.serumV3Usdt = serumV3Usdt259 self.serumV3Usdc = serumV3Usdc260 self.coingeckoId = coingeckoId261 self.imageUrl = imageUrl262 self.description = description263 }264}265