Anonymous
Как редактировать метаданные на фантомном кошельке, когда он предлагает подключиться из моего приложения Swiftui
Сообщение
Anonymous » 12 июл 2025, 09:09
В настоящее время мой код работает для подключения к Phantom Wallet, но я не уверен, как редактировать, как Phantom Wallet отображает информацию о моем приложении (фото и имя). Я попытался размещать сайт netlify index.html с метаданными для него, который тоже не сработал.
Код: Выделить всё
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.
Как он выглядит в Phantom Wallet:
Подробнее здесь:
https://stackoverflow.com/questions/796 ... from-my-sw
1752300581
Anonymous
В настоящее время мой код работает для подключения к Phantom Wallet, но я не уверен, как редактировать, как Phantom Wallet отображает информацию о моем приложении (фото и имя). Я попытался размещать сайт netlify index.html с метаданными для него, который тоже не сработал.[code]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. [/code] Как он выглядит в Phantom Wallet: Подробнее здесь: [url]https://stackoverflow.com/questions/79699042/how-to-edit-the-metadata-on-phantom-wallet-when-it-prompts-to-connect-from-my-sw[/url]