-
Notifications
You must be signed in to change notification settings - Fork 115
Expand file tree
/
Copy pathSIWEFromCacaoPayloadFormatter.swift
More file actions
121 lines (103 loc) · 4.59 KB
/
Copy pathSIWEFromCacaoPayloadFormatter.swift
File metadata and controls
121 lines (103 loc) · 4.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import Foundation
/// CAIP-122 Sign with X message formatting protocol
/// Supports multiple blockchain types (Ethereum, Bitcoin, Solana, etc.)
public protocol SignWithXFormatting {
func formatMessage(from payload: CacaoPayload, includeRecapInTheStatement: Bool) throws -> String
}
public extension SignWithXFormatting {
func formatMessage(from payload: CacaoPayload) throws -> String {
return try formatMessage(from: payload, includeRecapInTheStatement: true)
}
}
/// Legacy protocol name for backward compatibility
/// @deprecated Use SignWithXFormatting instead
public typealias SIWEFromCacaoFormatting = SignWithXFormatting
public typealias SignWithXFormatter = SIWEFromCacaoPayloadFormatter
/// CAIP-122 compliant formatter that supports multiple chain types
/// Previously SIWEFromCacaoPayloadFormatter - now supports Sign with X (CAIP-122)
public struct SIWEFromCacaoPayloadFormatter: SignWithXFormatting {
public enum Errors: Error, Equatable {
case invalidSingleLineField(String)
}
public init() {}
public func formatMessage(from payload: CacaoPayload, includeRecapInTheStatement: Bool) throws -> String {
let iss = try DIDPKH(did: payload.iss)
let account = iss.account
let address = account.address
let chainId = account.reference
let namespace = account.namespace
// EIP-4361 / CAIP-122 single-line fields must not embed CR/LF, or a
// caller can forge later lines (URI, Nonce, …). Parity with
// walletconnect-monorepo #7311 / walletconnect-utils #280.
try assertSingleLine(payload.domain, field: "domain")
try assertSingleLine(payload.aud, field: "aud")
try assertSingleLine(payload.version, field: "version")
try assertSingleLine(payload.nonce, field: "nonce")
try assertSingleLine(payload.iat, field: "iat")
if let statement = payload.statement {
try assertSingleLine(statement, field: "statement")
}
if let exp = payload.exp {
try assertSingleLine(exp, field: "exp")
}
if let nbf = payload.nbf {
try assertSingleLine(nbf, field: "nbf")
}
if let requestId = payload.requestId {
try assertSingleLine(requestId, field: "requestId")
}
// Determine chain-specific account type for CAIP-122
let accountType = getAccountType(for: namespace)
// Directly use the statement from payload, add a newline if it exists
let statementLine = payload.statement.flatMap { "\n\($0)" } ?? ""
// Format the message with chain-specific account type (CAIP-122)
let formattedMessage = """
\(payload.domain) wants you to sign in with your \(accountType) account:
\(address)
\(statementLine)
URI: \(payload.aud)
Version: \(payload.version)
Chain ID: \(chainId)
Nonce: \(payload.nonce)
Issued At: \(payload.iat)\(formatExpLine(exp: payload.exp))\(formatNbfLine(nbf: payload.nbf))\(formatRequestIdLine(requestId: payload.requestId))\(formatResourcesSection(resources: payload.resources))
"""
return formattedMessage
}
private func assertSingleLine(_ value: String, field: String) throws {
if value.contains("\r") || value.contains("\n") {
throw Errors.invalidSingleLineField(field)
}
}
// CAIP-122: Map chain namespace to account type
private func getAccountType(for namespace: String) -> String {
switch namespace {
case "eip155":
return "Ethereum"
case "bip122":
return "Bitcoin"
case "solana":
return "Solana"
default:
// Support any namespace by capitalizing it
return namespace.capitalized
}
}
// Helper methods for formatting individual parts of the message
private func formatExpLine(exp: String?) -> String {
guard let exp = exp else { return "" }
return "\nExpiration Time: \(exp)"
}
private func formatNbfLine(nbf: String?) -> String {
guard let nbf = nbf else { return "" }
return "\nNot Before: \(nbf)"
}
private func formatRequestIdLine(requestId: String?) -> String {
guard let requestId = requestId else { return "" }
return "\nRequest ID: \(requestId)"
}
private func formatResourcesSection(resources: [String]?) -> String {
guard let resources = resources else { return "" }
let resourcesList = resources.reduce("") { $0 + "\n- \($1)" }
return resources.isEmpty ? "\nResources:" : "\nResources:" + resourcesList
}
}