Skip to content
Open
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 apps/web/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
10 changes: 5 additions & 5 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@mui/icons-material": "^6.1.6",
"@mui/material": "^6.3.0",
"@mui/x-date-pickers": "^7.23.3",
"@next/third-parties": "^15.2.0",
"@next/third-parties": "^15.5.7",
"@reduxjs/toolkit": "^2.5.0",
"@reown/walletkit": "^1.2.1",
"@safe-global/api-kit": "^2.4.6",
Expand Down Expand Up @@ -76,7 +76,7 @@
"idb-keyval": "^6.2.1",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"next": "patch:next@15.2.3#../../.yarn/patches/next-npm-15.2.3-06a6671f62.patch",
"next": "15.5.7",
"papaparse": "^5.3.2",
"qrcode.react": "^3.1.0",
"react": "^19.0.0",
Expand All @@ -96,8 +96,8 @@
"@faker-js/faker": "^9.0.3",
"@mdx-js/loader": "^3.0.1",
"@mdx-js/react": "^3.0.1",
"@next/bundle-analyzer": "^15.0.4",
"@next/mdx": "^15.0.4",
"@next/bundle-analyzer": "^15.5.7",
"@next/mdx": "^15.5.7",
"@openzeppelin/contracts": "^4.9.6",
"@safe-global/safe-core-sdk-types": "^5.0.1",
"@safe-global/test": "workspace:^",
Expand Down Expand Up @@ -135,7 +135,7 @@
"cypress-file-upload": "^5.0.8",
"cypress-visual-regression": "^5.2.2",
"eslint": "^9.20.1",
"eslint-config-next": "^15.0.4",
"eslint-config-next": "^15.5.7",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-no-only-tests": "^3.3.0",
"eslint-plugin-prettier": "^5.2.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>NotifeeNotificationServiceExtension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
[APP_GROUPS_PLACEHOLDER]
</array>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import UserNotifications
import RNNotifeeCore
import MMKV
import SwiftCryptoTokenFormatter
import BigInt

struct ChainInfo: Codable {
let name: String
let symbol: String
let decimals: Int
}

struct ExtensionStore: Codable {
let chains: [String: ChainInfo]
let contacts: [String: String]
}

func loadExtensionStore() -> ExtensionStore? {
guard let groupDir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "[NOTIFICATION_APP_GROUP_IDENTIFIER]")?.path else {
NSLog("[NotifeeDebug] Failed to get app group directory")
return nil
}
guard let kv = MMKV(mmapID: "extension", cryptKey: nil, rootPath: groupDir, mode: .multiProcess, expectedCapacity: 0) else {
NSLog("[NotifeeDebug] Failed to open MMKV")
return nil
}
guard let json = kv.string(forKey: "notification-extension-data") else {
NSLog("[NotifeeDebug] No data found for notification-extension-data key")
return nil
}
guard let data = json.data(using: String.Encoding.utf8) else {
NSLog("[NotifeeDebug] Failed to convert json to data")
return nil
}
return try? JSONDecoder().decode(ExtensionStore.self, from: data)
}


func parseNotification(userInfo: [AnyHashable: Any], store: ExtensionStore) -> (String, String)? {
NSLog("[NotifeeDebug] Parsing notification with userInfo: \(userInfo)")

guard let type = userInfo["type"] as? String else {
NSLog("[NotifeeDebug] No type found in notification")
return nil
}
NSLog("[NotifeeDebug] Notification type: \(type)")

let chainId = userInfo["chainId"] as? String
let address = userInfo["address"] as? String

NSLog("[NotifeeDebug] ChainId: \(chainId ?? "nil"), Address: \(address ?? "nil")")

let chainInfo = chainId.flatMap { store.chains[$0] }
let chainName = chainInfo?.name ?? (chainId != nil ? "Chain Id \(chainId!)" : "")
let safeName = address.flatMap { store.contacts[$0] } ?? (address ?? "")

NSLog("[NotifeeDebug] Resolved chainName: \(chainName), safeName: \(safeName)")

switch type {
case "INCOMING_ETHER":
// Use chain symbol if available, otherwise fall back to userInfo or default
let symbol = chainInfo?.symbol ?? userInfo["symbol"] as? String ?? "ETH"
let formatter = TokenFormatter()
let value = userInfo["value"] as? String ?? ""
// Use chain decimals if available, otherwise fall back to userInfo or default
let decimals = chainInfo?.decimals ?? Int(userInfo["decimals"] as? String ?? "18") ?? 18
let amount = formatter.string(
from: BigDecimal(BigInt(value) ?? BigInt(0), decimals),
decimalSeparator: Locale.autoupdatingCurrent.decimalSeparator ?? ".",
thousandSeparator: Locale.autoupdatingCurrent.groupingSeparator ?? ",")

return ("Incoming \(symbol) (\(chainName))", "\(safeName): \(amount) \(symbol) received")
case "INCOMING_TOKEN":
return ("Incoming token (\(chainName))", "\(safeName): tokens received")
case "EXECUTED_MULTISIG_TRANSACTION":
let status = (userInfo["failed"] as? String) == "true" ? "failed" : "successful"
return ("Transaction \(status) (\(chainName))", "\(safeName): Transaction \(status)")
case "CONFIRMATION_REQUEST":
return ("Confirmation required (\(chainName))", "\(safeName): A transaction requires your confirmation!")
default:
return nil
}
}

class NotificationService: UNNotificationServiceExtension {
let appGroup = "[NOTIFICATION_APP_GROUP_IDENTIFIER]"
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?

override init() {
super.init()
if let groupDir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)?.path {
MMKV.initialize(rootDir: groupDir)
} else {
NSLog("[NotifeeDebug] Failed to initialize MMKV: couldn't get app group directory")
}
}

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
NSLog("[NotifeeDebug] Received notification request with id: \(request.identifier)")
self.contentHandler = contentHandler
self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

if let mutable = self.bestAttemptContent {
NSLog("[NotifeeDebug] Successfully created mutable content")

if let store = loadExtensionStore() {
NSLog("[NotifeeDebug] Successfully loaded extension store")

if let parsed = parseNotification(userInfo: request.content.userInfo, store: store) {
NSLog("[NotifeeDebug] Successfully parsed notification: title=\(parsed.0), body=\(parsed.1)")
mutable.title = parsed.0
mutable.body = parsed.1
mutable.badge = 1
} else {
NSLog("[NotifeeDebug] Failed to parse notification")
}
} else {
NSLog("[NotifeeDebug] Failed to load extension store")
}

NotifeeExtensionHelper.populateNotificationContent(request, with: mutable, withContentHandler: contentHandler)
} else {
NSLog("[NotifeeDebug] Failed to create mutable content")
contentHandler(request.content)
}
}

override func serviceExtensionTimeWillExpire() {
NSLog("[NotifeeDebug] Service extension time will expire")
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// BoolString.swift
// Multisig
//
// Created by Dmitry Bespalov on 26.07.21.
// Copyright © 2021 Gnosis Ltd. All rights reserved.
//

import Foundation

struct BoolString: Hashable, Codable {
var value: Bool

init(_ value: Bool) {
self.value = value
}

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
value = string == "true"
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value ? "true" : "false")
}
}

extension BoolString: ExpressibleByStringLiteral {
init(stringLiteral value: StringLiteralType) {
self.init(value == "true")
}
}

extension BoolString: CustomStringConvertible {
var description: String {
String(describing: value)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// CharacterSet+Hex.swift
// Multisig
//
// Created by Moaaz on 5/22/20.
// Copyright © 2020 Gnosis Ltd. All rights reserved.
//

import Foundation

extension CharacterSet {

static var hexadecimalNumbers: CharacterSet {
return ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
}

static var hexadecimalLetters: CharacterSet {
return ["a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F"]
}

static var hexadecimals: CharacterSet {
return hexadecimalNumbers.union(hexadecimalLetters)
}
}
Loading