diff --git a/SDK/Runtime/Auth/Models/IPrivyUser.cs b/SDK/Runtime/Auth/Models/IPrivyUser.cs index 604b941..82f0d47 100644 --- a/SDK/Runtime/Auth/Models/IPrivyUser.cs +++ b/SDK/Runtime/Auth/Models/IPrivyUser.cs @@ -89,5 +89,29 @@ public interface IPrivyUser /// Creates a new Solana embedded wallet for the user. /// Task CreateSolanaWallet(bool allowAdditional = false); + + /// + /// Generates a cryptographic signature for an API request payload using the + /// authenticated user's signing key. + /// + /// This method canonicalizes the payload to JSON (RFC 8785) and signs the + /// canonical bytes. The returned base64-encoded signature can be included as + /// the privy-authorization-signature header to authorize Privy API requests. + /// + /// The containing the API request details to be signed. + /// A task whose result is the base64-encoded signature string. + Task GenerateAuthorizationSignature(WalletApiPayload payload); + + /// + /// Generates a cryptographic signature for a pre-serialized binary payload using the + /// authenticated user's signing key. + /// + /// Unlike the typed payload variant, this method skips JSON canonicalization and signs the + /// raw bytes directly. Use this when you have already serialized and canonicalized your + /// payload outside the SDK. + /// + /// The pre-serialized payload bytes to sign. + /// A task whose result is the base64-encoded signature string. + Task GenerateAuthorizationSignature(byte[] payload); } } diff --git a/SDK/Runtime/Auth/Models/PrivyUser.cs b/SDK/Runtime/Auth/Models/PrivyUser.cs index ae4cde1..cb27ce2 100644 --- a/SDK/Runtime/Auth/Models/PrivyUser.cs +++ b/SDK/Runtime/Auth/Models/PrivyUser.cs @@ -98,6 +98,7 @@ public Dictionary CustomMetadata private AppConfigRepository _appConfigRepository; private WalletApiWalletCreator _walletApiWalletCreator; private WalletApiRepository _walletApiRepository; + private IAuthorizationKey _authorizationKey; private EmbeddedWalletManager _embeddedWalletManager; @@ -105,13 +106,14 @@ public Dictionary CustomMetadata //TODO: Create Interface class for this internal PrivyUser(AuthDelegator authDelegator, EmbeddedWalletManager embeddedWalletManager, AppConfigRepository appConfigRepository, WalletApiWalletCreator walletApiWalletCreator, - WalletApiRepository walletApiRepository) + WalletApiRepository walletApiRepository, IAuthorizationKey authorizationKey) { _authDelegator = authDelegator; _embeddedWalletManager = embeddedWalletManager; _appConfigRepository = appConfigRepository; _walletApiWalletCreator = walletApiWalletCreator; _walletApiRepository = walletApiRepository; + _authorizationKey = authorizationKey; } public async Task GetAccessToken() @@ -124,6 +126,19 @@ public async Task GetIdentityToken() return await _authDelegator.GetIdentityToken(); } + public async Task GenerateAuthorizationSignature(WalletApiPayload payload) + { + byte[] encodedPayload = payload.EncodePayload(); + byte[] signature = await _authorizationKey.Signature(encodedPayload); + return Convert.ToBase64String(signature); + } + + public async Task GenerateAuthorizationSignature(byte[] payload) + { + byte[] signature = await _authorizationKey.Signature(payload); + return Convert.ToBase64String(signature); + } + // Public Methods public async Task CreateEthereumWallet(bool allowAdditional = false) { diff --git a/SDK/Runtime/Core/PrivyImpl.cs b/SDK/Runtime/Core/PrivyImpl.cs index 8451658..189145b 100644 --- a/SDK/Runtime/Core/PrivyImpl.cs +++ b/SDK/Runtime/Core/PrivyImpl.cs @@ -119,7 +119,7 @@ public PrivyImpl(PrivyConfig config) OAuth = new LoginWithOAuth(_authDelegator); Sms = new LoginWithSms(_authDelegator); _user = new PrivyUser(_authDelegator, _embeddedWalletManager, _appConfigRepository, walletApiWalletCreator, - walletApiRepository); + walletApiRepository, authorizationKey); } internal async Task InitializeAsync() diff --git a/SDK/Runtime/EmbeddedWallet/WalletApi/WalletAuthorization/WalletApiPayload.cs b/SDK/Runtime/EmbeddedWallet/WalletApi/WalletAuthorization/WalletApiPayload.cs index 11d757c..281549c 100644 --- a/SDK/Runtime/EmbeddedWallet/WalletApi/WalletAuthorization/WalletApiPayload.cs +++ b/SDK/Runtime/EmbeddedWallet/WalletApi/WalletAuthorization/WalletApiPayload.cs @@ -4,22 +4,26 @@ namespace Privy.Wallets { - internal struct WalletApiPayload + /// + /// Represents an API request payload to be signed for authorization. + /// The signature can be included as the privy-authorization-signature header. + /// + public struct WalletApiPayload { [JsonProperty("version")] - internal int Version; + public int Version; [JsonProperty("url")] - internal string Url; + public string Url; [JsonProperty("method")] - internal string Method; + public string Method; [JsonProperty("headers")] - internal Dictionary Headers; + public Dictionary Headers; [JsonProperty("body")] - internal object Body; + public object Body; internal byte[] EncodePayload() { diff --git a/SampleApp/Assets/Scenes/SampleScene.unity b/SampleApp/Assets/Scenes/SampleScene.unity index f82cbc8..07f7e7d 100644 --- a/SampleApp/Assets/Scenes/SampleScene.unity +++ b/SampleApp/Assets/Scenes/SampleScene.unity @@ -1037,6 +1037,127 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &353030993 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 353030994} + - component: {fileID: 353030996} + - component: {fileID: 353030995} + - component: {fileID: 353030997} + m_Layer: 5 + m_Name: authSigPayloadButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &353030994 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 353030993} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1658901497} + m_Father: {fileID: 8536368014042316539} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 209.8, y: 21.000042} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &353030995 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 353030993} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &353030996 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 353030993} + m_CullTransparentMesh: 1 +--- !u!114 &353030997 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 353030993} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 353030995} + m_OnClick: + m_PersistentCalls: + m_Calls: [] --- !u!1 &364446159 GameObject: m_ObjectHideFlags: 0 @@ -2243,6 +2364,140 @@ MonoBehaviour: initialScreenController: {fileID: 226325428} authScreenController: {fileID: 1482716558} walletScreenController: {fileID: 1959496582} +--- !u!1 &674726487 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 674726488} + - component: {fileID: 674726490} + - component: {fileID: 674726489} + m_Layer: 5 + m_Name: authSignSectionTitle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &674726488 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 674726487} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8536368014042316539} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 333, y: 36} + m_SizeDelta: {x: 300, y: 29.9451} + m_Pivot: {x: 0.5, y: 1.5034301} +--- !u!114 &674726489 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 674726487} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Auth Sign with Server interop + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 20 + m_fontSizeBase: 20 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &674726490 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 674726487} + m_CullTransparentMesh: 1 --- !u!1 &701174076 GameObject: m_ObjectHideFlags: 0 @@ -2657,7 +2912,7 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 0} ---- !u!1 &943285432 +--- !u!1 &933207223 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -2665,45 +2920,47 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 943285433} - - component: {fileID: 943285435} - - component: {fileID: 943285434} + - component: {fileID: 933207224} + - component: {fileID: 933207226} + - component: {fileID: 933207225} + - component: {fileID: 933207227} m_Layer: 5 - m_Name: Text (TMP) + m_Name: authSigBinaryButton m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &943285433 +--- !u!224 &933207224 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 943285432} + m_GameObject: {fileID: 933207223} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 2120685268} + m_Children: + - {fileID: 1713075061} + m_Father: {fileID: 8536368014042316539} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 209.80003, y: -36.999958} + m_SizeDelta: {x: 160, y: 30} m_Pivot: {x: 0.5, y: 0.5} ---- !u!114 &943285434 +--- !u!114 &933207225 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 943285432} + m_GameObject: {fileID: 933207223} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} @@ -2714,15 +2971,134 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: 'SMS Login - -' - m_isRightToLeft: 0 - m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} - m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} - m_fontSharedMaterials: [] - m_fontMaterial: {fileID: 0} - m_fontMaterials: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &933207226 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 933207223} + m_CullTransparentMesh: 1 +--- !u!114 &933207227 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 933207223} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 933207225} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &943285432 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 943285433} + - component: {fileID: 943285435} + - component: {fileID: 943285434} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &943285433 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 943285432} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2120685268} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &943285434 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 943285432} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: 'SMS Login + +' + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] m_fontColor32: serializedVersion: 2 rgba: 4281479730 @@ -4816,7 +5192,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 22400762, guid: 67117722a812a2e46ab8cb8eafbf5f5e, type: 3} propertyPath: m_AnchoredPosition.y - value: 0 + value: 0.000011444092 objectReference: {fileID: 0} - target: {fileID: 22426080, guid: 67117722a812a2e46ab8cb8eafbf5f5e, type: 3} propertyPath: m_AnchorMax.x @@ -4826,6 +5202,10 @@ PrefabInstance: propertyPath: m_AnchorMax.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 22426080, guid: 67117722a812a2e46ab8cb8eafbf5f5e, type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} - target: {fileID: 22428984, guid: 67117722a812a2e46ab8cb8eafbf5f5e, type: 3} propertyPath: m_AnchorMax.y value: 0 @@ -5613,6 +5993,140 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1615527574} m_CullTransparentMesh: 1 +--- !u!1 &1645388276 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1645388279} + - component: {fileID: 1645388278} + - component: {fileID: 1645388277} + m_Layer: 5 + m_Name: authSigResult + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1645388277 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1645388276} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: 'Result: ' + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 20 + m_fontSizeBase: 20 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &1645388278 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1645388276} + m_CullTransparentMesh: 1 +--- !u!224 &1645388279 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1645388276} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8536368014042316539} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 333, y: -95.6} + m_SizeDelta: {x: 300, y: 29.9451} + m_Pivot: {x: 0.5, y: 1.5034301} --- !u!1 &1645524104 GameObject: m_ObjectHideFlags: 0 @@ -5881,6 +6395,140 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1657530412} m_CullTransparentMesh: 1 +--- !u!1 &1658901496 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1658901497} + - component: {fileID: 1658901499} + - component: {fileID: 1658901498} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1658901497 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1658901496} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 353030994} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1658901498 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1658901496} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Sign Struct + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 24 + m_fontSizeBase: 24 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &1658901499 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1658901496} + m_CullTransparentMesh: 1 --- !u!1 &1662613036 GameObject: m_ObjectHideFlags: 0 @@ -6232,6 +6880,140 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1683759933} m_CullTransparentMesh: 1 +--- !u!1 &1713075060 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1713075061} + - component: {fileID: 1713075063} + - component: {fileID: 1713075062} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1713075061 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1713075060} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 933207224} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1713075062 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1713075060} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Sign Bytes + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 24 + m_fontSizeBase: 24 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &1713075063 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1713075060} + m_CullTransparentMesh: 1 --- !u!1 &1742386644 GameObject: m_ObjectHideFlags: 0 @@ -6964,6 +7746,9 @@ MonoBehaviour: solanaWalletsDropdown: {fileID: 701174078} createWalletWithIndexButton: {fileID: 1331771588} hdWalletIndexInput: {fileID: 1023113672} + authSigPayloadButton: {fileID: 353030997} + authSigBinaryButton: {fileID: 933207227} + authSigResult: {fileID: 1645388277} --- !u!1 &1973866089 GameObject: m_ObjectHideFlags: 0 @@ -13488,6 +14273,10 @@ RectTransform: - {fileID: 1287959958} - {fileID: 1009098529} - {fileID: 8293069964063194094} + - {fileID: 674726488} + - {fileID: 353030994} + - {fileID: 933207224} + - {fileID: 1645388279} - {fileID: 1608502222} - {fileID: 7698815398252323425} - {fileID: 390634339} diff --git a/SampleApp/Assets/Scripts/InitialScreenController.cs b/SampleApp/Assets/Scripts/InitialScreenController.cs index 5944ce0..d246aeb 100644 --- a/SampleApp/Assets/Scripts/InitialScreenController.cs +++ b/SampleApp/Assets/Scripts/InitialScreenController.cs @@ -1,5 +1,6 @@ using System; using Privy; +using Privy.Auth; using Privy.Auth.Models; using Privy.Config; using Privy.Core; @@ -49,8 +50,10 @@ private void Awake() private async void Start() { - await PrivyManager.Instance.GetAuthState(); + var state = await PrivyManager.Instance.GetAuthState(); Debug.Log("PrivyManager is ready."); + if (state == AuthState.Authenticated) + UIManager.Instance.ShowAuthorizedScreen(); } private void OnLoginWithEmailButtonClick() diff --git a/SampleApp/Assets/Scripts/WalletController.cs b/SampleApp/Assets/Scripts/WalletController.cs index 4f8b2d2..1e819bc 100644 --- a/SampleApp/Assets/Scripts/WalletController.cs +++ b/SampleApp/Assets/Scripts/WalletController.cs @@ -1,8 +1,10 @@ using System; using System.Linq; +using System.Text; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; +using UnityEngine.Networking; using TMPro; using Newtonsoft.Json; using Privy.Core; @@ -36,6 +38,12 @@ public class WalletController : MonoBehaviour public Button createWalletWithIndexButton; public TMP_InputField hdWalletIndexInput; + public Button authSigPayloadButton; + public Button authSigBinaryButton; + public TextMeshProUGUI authSigResult; + + private const string INTERNAL_DEMO_URL = "http://localhost:3200"; + private IPrivyUser _privyUser; private void Awake() @@ -50,6 +58,8 @@ private void Awake() createWalletWithIndexButton.onClick.AddListener(OnCreateEthereumWalletWithIndex); solanaSignButton.onClick.AddListener(OnSolanaSignButtonClick); + authSigPayloadButton.onClick.AddListener(OnAuthSigPayloadButtonClick); + authSigBinaryButton.onClick.AddListener(OnAuthSigBinaryButtonClick); } private async void OnCreateWalletButtonClick() @@ -243,6 +253,146 @@ private async void OnSolanaSignButtonClick() } } + [Serializable] + private class FormatPayloadApiResponse + { + public string payload; + public string url; + public FormatPayloadBody body; + } + + [Serializable] + private class FormatPayloadBody + { + public string method; + public FormatPayloadParams @params; + } + + [Serializable] + private class FormatPayloadParams + { + public string message; + public string encoding; + } + + [Serializable] + private class BinarySignApiResponse + { + public bool success; + } + + [Serializable] + private class BinarySignApiRequest + { + public string wallet_id; + public string signature; + } + + [Serializable] + private class FormatPayloadApiRequest + { + public string wallet_id; + } + + private async void OnAuthSigPayloadButtonClick() + { + try + { + IEmbeddedEthereumWallet wallet = SelectedWallet; + string walletId = wallet.Id; + string accessToken = await _privyUser.GetAccessToken(); + string appId = EnvFileReader.Get("PRIVY_APP_ID"); + + // Step 1: Get payload structure from server + var formatResponse = await PostJson( + $"{INTERNAL_DEMO_URL}/api/format_authorization_payload", + JsonConvert.SerializeObject(new FormatPayloadApiRequest { wallet_id = walletId })); + + // Step 2: Build WalletApiPayload from the returned url and body + var payload = new WalletApiPayload + { + Version = 1, + Url = formatResponse.url, + Method = "POST", + Headers = new Dictionary { { "privy-app-id", appId } }, + Body = formatResponse.body + }; + + // Step 3: Sign with the typed payload overload + string sig = await _privyUser.GenerateAuthorizationSignature(payload); + Debug.Log("Auth sig (payload): " + sig); + + // Step 4: Verify signature via server + await PostJson( + $"{INTERNAL_DEMO_URL}/api/test_binary_sign", + JsonConvert.SerializeObject(new BinarySignApiRequest { wallet_id = walletId, signature = sig }), + accessToken); + + authSigResult.text = "Payload sig OK: " + sig.Substring(0, Math.Min(30, sig.Length)) + "..."; + } + catch (Exception ex) + { + Debug.LogError($"Auth sig (payload) failed: {ex.Message}"); + authSigResult.text = "Payload sig FAILED: " + ex.Message; + } + } + + private async void OnAuthSigBinaryButtonClick() + { + try + { + IEmbeddedEthereumWallet wallet = SelectedWallet; + string walletId = wallet.Id; + string accessToken = await _privyUser.GetAccessToken(); + + // Step 1: Get binary payload from server + var formatResponse = await PostJson( + $"{INTERNAL_DEMO_URL}/api/format_authorization_payload", + JsonConvert.SerializeObject(new FormatPayloadApiRequest { wallet_id = walletId })); + + // Step 2: Decode base64 to byte[] and sign + byte[] payloadBytes = Convert.FromBase64String(formatResponse.payload); + string sig = await _privyUser.GenerateAuthorizationSignature(payloadBytes); + Debug.Log("Auth sig (binary): " + sig); + + // Step 3: Verify signature via server + await PostJson( + $"{INTERNAL_DEMO_URL}/api/test_binary_sign", + JsonConvert.SerializeObject(new BinarySignApiRequest { wallet_id = walletId, signature = sig }), + accessToken); + + authSigResult.text = "Binary sig OK: " + sig.Substring(0, Math.Min(30, sig.Length)) + "..."; + } + catch (Exception ex) + { + Debug.LogError($"Auth sig (binary) failed: {ex.Message}"); + authSigResult.text = "Binary sig FAILED: " + ex.Message; + } + } + + private static async System.Threading.Tasks.Task PostJson(string url, string jsonBody, string bearerToken = null) + { + using var request = new UnityWebRequest(url, "POST"); + byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonBody); + request.uploadHandler = new UploadHandlerRaw(bodyRaw); + request.downloadHandler = new DownloadHandlerBuffer(); + request.SetRequestHeader("Content-Type", "application/json"); + if (bearerToken != null) + { + request.SetRequestHeader("Authorization", $"Bearer {bearerToken}"); + } + + var op = request.SendWebRequest(); + while (!op.isDone) await System.Threading.Tasks.Task.Yield(); + + if (request.result != UnityWebRequest.Result.Success) + { + throw new Exception($"HTTP {request.responseCode}: {request.downloadHandler.text}"); + } + + return JsonConvert.DeserializeObject(request.downloadHandler.text); + } + private void OnBackButtonClick() { UIManager.Instance.GoBack();