Код: Выделить всё
import SwiftUI
import CryptoKit
struct Base58 {
private static let alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
private static let base = UInt32(alphabet.count)
static func encode(_ data: Data) -> String {
var x = data
var answer = ""
var leadingZeros = 0
for byte in x {
if byte == 0 { leadingZeros += 1 }
else { break }
}
var digits = [UInt8]()
while !x.isEmpty {
var remainder: UInt32 = 0
var temp = Data()
for byte in x {
let value = UInt32(byte) + remainder * 256
let digit = value / base
remainder = value % base
if temp.isEmpty && digit == 0 { continue }
temp.append(UInt8(digit))
}
digits.append(UInt8(remainder))
x = temp
}
answer = String(repeating: "1", count: leadingZeros)
for digit in digits.reversed() {
answer.append(alphabet[alphabet.index(alphabet.startIndex, offsetBy: Int(digit))])
}
return answer
}
static func decode(_ string: String) -> Data? {
var answer = Data()
for char in string {
guard let charIndex = alphabet.firstIndex(of: char)?.utf16Offset(in: alphabet) else {
return nil
}
var carry = UInt32(charIndex)
var temp = Data()
for byte in answer.reversed() {
let value = UInt32(byte) * base + carry
carry = value / 256
temp.insert(UInt8(value % 256), at: 0)
}
while carry > 0 {
temp.insert(UInt8(carry % 256), at: 0)
carry /= 256
}
answer = temp
}
var leadingOnes = 0
for char in string {
if char == "1" { leadingOnes += 1 }
else { break }
}
let leadingZeros = Data(repeating: 0, count: leadingOnes)
return leadingZeros + answer
}
}
struct PhantomWalletView: View {
@State private var isConnected = false
@State private var publicKey: String = ""
@State private var session: String = ""
@State private var dappKeyPair: Curve25519.KeyAgreement.PrivateKey?
@State private var sharedSecret: SymmetricKey?
var body: some View {
VStack(spacing: 20) {
Text("Phantom Wallet Integration")
.font(.title)
if isConnected {
VStack(spacing: 15) {
Text("Connected!")
.foregroundColor(.green)
.font(.headline)
Text("Public Key: \(publicKey)")
.font(.caption)
.multilineTextAlignment(.center)
.padding(.horizontal)
Button("Sign Message") {
signMessage()
}
.buttonStyle(.borderedProminent)
Button("Disconnect") {
disconnect()
}
.buttonStyle(.bordered)
}
} else {
Button("Connect to Phantom") {
connectToPhantom()
}
.buttonStyle(.borderedProminent)
}
}
.padding()
.onOpenURL { url in
handlePhantomResponse(url: url)
}
}
// MARK: - Phantom Integration Methods
func connectToPhantom() {
dappKeyPair = Curve25519.KeyAgreement.PrivateKey()
let appURL = "https://coinclip.netlify.app"
let redirectLink = "coinclip://phantom-response"
let dappPublicKey = dappKeyPair!.publicKey.rawRepresentation
var components = URLComponents(string: "https://phantom.app/ul/v1/connect")!
components.queryItems = [
URLQueryItem(name: "app_url", value: appURL),
URLQueryItem(name: "dapp_encryption_public_key", value: Base58.encode(dappPublicKey)),
URLQueryItem(name: "redirect_link", value: redirectLink),
URLQueryItem(name: "cluster", value: "mainnet-beta")
]
if let url = components.url {
UIApplication.shared.open(url)
}
}
func signMessage() {
guard let dappKeyPair = dappKeyPair,
let sharedSecret = sharedSecret,
!session.isEmpty else {
print("Cannot sign message: missing keypair, shared secret, or session.")
return
}
let message = "Hello from your SwiftUI dApp!"
let payloadDict: [String: Any] = ["message": message, "session": session, "display": "utf8"]
guard let payloadData = try? JSONSerialization.data(withJSONObject: payloadDict),
let (encryptedPayloadData, nonceData) = encryptData(payloadData, with: sharedSecret) else {
return
}
let redirectLink = "coinclip://phantom-response"
var components = URLComponents(string: "https://phantom.app/ul/v1/signMessage")!
components.queryItems = [
URLQueryItem(name: "dapp_encryption_public_key", value: Base58.encode(dappKeyPair.publicKey.rawRepresentation)),
URLQueryItem(name: "nonce", value: Base58.encode(nonceData)),
URLQueryItem(name: "redirect_link", value: redirectLink),
URLQueryItem(name: "payload", value: Base58.encode(encryptedPayloadData))
]
if let url = components.url {
UIApplication.shared.open(url)
}
}
func disconnect() {
isConnected = false
publicKey = ""
session = ""
dappKeyPair = nil
sharedSecret = nil
}
func handlePhantomResponse(url: URL) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems else { return }
let params = queryItems.reduce(into: [String: String]()) { $0[$1.name] = $1.value }
if let errorCode = params["errorCode"] {
print("Phantom error: \(errorCode) - \(params["errorMessage"] ?? "Unknown error")")
return
}
// Handle connect response
if let phantomPublicKeyString = params["phantom_encryption_public_key"],
let nonceString = params["nonce"],
let dataString = params["data"] {
guard let dappKeyPair = self.dappKeyPair,
let phantomPublicKeyData = Base58.decode(phantomPublicKeyString),
let nonceData = Base58.decode(nonceString),
let encryptedData = Base58.decode(dataString) else {
print("Failed to decode response or missing dApp keypair.")
return
}
do {
let phantomPublicKey = try Curve25519.KeyAgreement.PublicKey(rawRepresentation: phantomPublicKeyData)
let secret = try dappKeyPair.sharedSecretFromKeyAgreement(with: phantomPublicKey)
let symmetricKey = secret.hkdfDerivedSymmetricKey(
using: SHA256.self, salt: Data(), sharedInfo: Data(), outputByteCount: 32
)
self.sharedSecret = symmetricKey
if let decryptedData = decryptData(encryptedData: encryptedData, nonceData: nonceData, with: symmetricKey),
let json = try JSONSerialization.jsonObject(with: decryptedData) as? [String: Any] {
self.publicKey = json["public_key"] as? String ?? ""
self.session = json["session"] as? String ?? ""
self.isConnected = true
print("Successfully connected to Phantom!")
}
} catch {
print("Error handling connect response: \(error)")
}
}
// Handle sign message response
if let signatureString = params["signature"] {
if let signatureData = Base58.decode(signatureString) {
print("Message signed successfully! Signature: \(signatureData.map { String(format: "%02hhx", $0) }.joined())")
}
}
}
// MARK: - Crypto Helper Methods
func encryptData(_ data: Data, with key: SymmetricKey) -> (payload: Data, nonce: Data)? {
do {
let sealedBox = try ChaChaPoly.seal(data, using: key)
let nonceData = sealedBox.nonce.withUnsafeBytes { Data($0) }
let payload = sealedBox.ciphertext + sealedBox.tag
return (payload: payload, nonce: nonceData)
} catch {
print("Error: Encryption failed - \(error)")
return nil
}
}
func decryptData(encryptedData: Data, nonceData: Data, with key: SymmetricKey) -> Data? {
guard encryptedData.count > 16 else {
print("Error: Encrypted data is too short to contain a tag.")
return nil
}
let tagIndex = encryptedData.count - 16
let ciphertext = encryptedData.prefix(upTo: tagIndex)
let tag = encryptedData.suffix(from: tagIndex)
do {
let nonce = try ChaChaPoly.Nonce(data: nonceData)
// Reconstruct the SealedBox with the separated nonce, ciphertext, and tag.
let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: tag)
return try ChaChaPoly.open(sealedBox, using: key)
} catch {
print("Error: Decryption failed - \(error)")
return nil
}
}
}
// MARK: - Extensions
extension Data {
/// Encodes data into a Base58 string.
func base58EncodedString() -> String {
return Base58.encode(self)
}
}
< /code>
my index.html code: < /p>
CoinClip
Coinclip App Metadata
This page provides the necessary metadata for Phantom Wallet integration.
Подробнее здесь: https://stackoverflow.com/questions/796 ... from-my-sw