Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Swift SDK docs #1005

Merged
merged 5 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,54 @@ Note that when the transaction has been signed anyone with the signature and the

.. tab::

Swift (iOS)
Swift (macOS, iOS)

.. code-block:: Swift

import Concordium

// Inputs.
let seedPhrase = "fence tongue sell large master side flock bronze ice accident what humble bring heart swear record valley party jar caution horn cushion endorse position"
let network = Network.testnet
let identityProviderID = IdentityProviderID(3)
let identityIndex = IdentityIndex(7)
let credentialCounter = CredentialCounter(21)
let amount = MicroCCDAmount(1337)
let receiver = try! AccountAddress(base58Check: "33Po4Z5v4DaAHo9Gz9Afc9LRzbZmYikus4Q7gqMaXHtdS17khz")
let expiry = TransactionTime(9_999_999_999)

/// Perform a transfer based on the inputs above.
func transfer(client: NodeClient) async throws {
let seed = try decodeSeed(seedPhrase, network)

// Derive seed based account from the given coordinates of the given seed.
let cryptoParams = try await client.cryptographicParameters(block: .lastFinal)
let accountDerivation = SeedBasedAccountDerivation(seed: seed, cryptoParams: cryptoParams)
let credentialIndexes = AccountCredentialSeedIndexes(
identity: .init(providerID: identityProviderID, index: identityIndex),
counter: credentialCounter
)
let account = try accountDerivation.deriveAccount(credentials: [credentialIndexes])

// Construct, sign, and send transfer transaction.
let nextSeq = try await client.nextAccountSequenceNumber(address: account.address)
let tx = try makeTransfer(account, amount, receiver, nextSeq.sequenceNumber, expiry)
let hash = try await client.send(transaction: tx)
print("Transaction with hash '\(hash.hex)' successfully submitted.")
}

/// Construct and sign transfer transaction.
func makeTransfer(
_ account: Account,
_ amount: MicroCCDAmount,
_ receiver: AccountAddress,
_ seq: SequenceNumber,
_ expiry: TransactionTime
) throws -> SignedAccountTransaction {
let tx = AccountTransaction(sender: account.address, payload: .transfer(amount: amount, receiver: receiver))
return try account.keys.sign(transaction: tx, sequenceNumber: seq, expiry: expiry)
}

The Swift SDK for iOS is still in development.

++++++++++++++++++++++++++++++++++++++++++++++++
Send an account transaction to a Concordium node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,82 @@ The following example demonstrates how a credential deployment transaction is cr

.. tab::

Swift (iOS)
Swift (macOS, iOS)

.. code-block:: Swift

import Concordium
import Foundation

// Inputs.
let seedPhrase = "fence tongue sell large master side flock bronze ice accident what humble bring heart swear record valley party jar caution horn cushion endorse position"
let network = Network.testnet
let identityProviderID = IdentityProviderID(3)
let identityIndex = IdentityIndex(7)
let credentialCounter = CredentialCounter(21)
let walletProxyBaseURL = URL(string: "https://wallet-proxy.testnet.concordium.com")!
let anonymityRevocationThreshold = RevocationThreshold(2)
let expiry = TransactionTime(9_999_999_999)

/// Perform account creation (on recovered identity) based on the inputs above.
func createAccount(client: NodeClient) async throws {
let seed = try decodeSeed(seedPhrase, network)
let walletProxy = WalletProxy(baseURL: walletProxyBaseURL)
let identityProvider = try await findIdentityProvider(walletProxy, identityProviderID)!

// Recover identity (not necessary if the ID is already stored).
// This assumes that the identity already exists, of course.
let cryptoParams = try await client.cryptographicParameters(block: .lastFinal)
let identityReq = try makeIdentityRecoveryRequest(seed, cryptoParams, identityProvider, identityIndex)
let identity = try await identityReq.send(session: URLSession.shared)

// Derive seed based credential and account from the given coordinates of the given seed.
let accountDerivation = SeedBasedAccountDerivation(seed: seed, cryptoParams: cryptoParams)
let seedIndexes = AccountCredentialSeedIndexes(
identity: .init(providerID: identityProviderID, index: identityIndex),
counter: credentialCounter
)
// Credential to deploy.
let credential = try accountDerivation.deriveCredential(
seedIndexes: seedIndexes,
identity: identity.value,
provider: identityProvider,
threshold: 1
)
// Account used to sign the deployment.
// The account is composed from just the credential derived above.
// From this call the credential's signing key will be derived;
// in the previous only the public key was.
let account = try accountDerivation.deriveAccount(credentials: [seedIndexes])

// Construct, sign, and send deployment transaction.
let signedTx = try account.keys.sign(deployment: credential, expiry: expiry)
let serializedTx = try signedTx.serialize()
let hash = try await client.send(deployment: serializedTx)
print("Transaction with hash '\(hash.hex)' successfully submitted.")
}

The Swift SDK for iOS is still in development.
func makeIdentityRecoveryRequest(
_ seed: WalletSeed,
_ cryptoParams: CryptographicParameters,
_ identityProvider: IdentityProvider,
_ identityIndex: IdentityIndex
bisgardo marked this conversation as resolved.
Show resolved Hide resolved
) throws -> IdentityRecoverRequest {
let identityRequestBuilder = SeedBasedIdentityRequestBuilder(
seed: seed,
cryptoParams: cryptoParams
)
let reqJSON = try identityRequestBuilder.recoveryRequestJSON(
provider: identityProvider.info,
index: identityIndex,
time: Date.now
)
let urlBuilder = IdentityRequestURLBuilder(callbackURL: nil)
return try urlBuilder.recoveryRequest(
baseURL: identityProvider.metadata.recoveryStart,
requestJSON: reqJSON
)
}

++++++++++++++++++++++++++++++++++++++++
Sign a credential deployment transaction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,100 @@ The first step is to create the actual identity request. To do this, you need th

.. tab::

Swift (iOS)
Swift (macOS, iOS)

.. code-block:: Swift

import Concordium
import Foundation

// Inputs.
let seedPhrase = "fence tongue sell large master side flock bronze ice accident what humble bring heart swear record valley party jar caution horn cushion endorse position"
let network = Network.testnet
let identityProviderID = IdentityProviderID(3)
let identityIndex = IdentityIndex(7)
let walletProxyBaseURL = URL(string: "https://wallet-proxy.testnet.concordium.com")!
let anonymityRevocationThreshold = RevocationThreshold(2)

/// Perform an identity creation based on the inputs above.
func createIdentity(client: NodeClient) async throws {
let seed = try decodeSeed(seedPhrase, network)
let walletProxy = WalletProxy(baseURL: walletProxyBaseURL)
let identityProvider = try await findIdentityProvider(walletProxy, identityProviderID)!

// Construct identity creation request and start verification.
let cryptoParams = try await client.cryptographicParameters(block: .lastFinal)
let identityReq = try issueIdentitySync(seed, cryptoParams, identityProvider, identityIndex, anonymityRevocationThreshold) { issuanceStartURL, requestJSON in
// The URL to be invoked when once the ID verification process has started (i.e. once the data has been filled in).
let callbackURL = URL(string: "concordiumwallet-example://identity-issuer/callback")!

let urlBuilder = IdentityRequestURLBuilder(callbackURL: callbackURL)
let url = try urlBuilder.issuanceURLToOpen(baseURL: issuanceStartURL, requestJSON: requestJSON)
todoOpenURL(url)

return todoAwaitCallbackWithVerificationPollingURL()
}

The Swift SDK for iOS is still in development.
let res = try await todoFetchIdentityIssuance(identityReq)
if case let .success(identity) = res {
print("Identity issued successfully: \(identity))")
} else {
// Verification failed...
}
}

func issueIdentitySync(
_ seed: WalletSeed,
_ cryptoParams: CryptographicParameters,
_ identityProvider: IdentityProvider,
_ identityIndex: IdentityIndex,
_ anonymityRevocationThreshold: RevocationThreshold,
_ runIdentityProviderFlow: (_ issuanceStartURL: URL, _ requestJSON: String) throws -> URL
) throws -> IdentityIssuanceRequest {
print("Preparing identity issuance request.")
let identityRequestBuilder = SeedBasedIdentityRequestBuilder(
seed: seed,
cryptoParams: cryptoParams
)
let reqJSON = try identityRequestBuilder.issuanceRequestJSON(
provider: identityProvider,
index: identityIndex,
anonymityRevocationThreshold: anonymityRevocationThreshold
)

print("Start identity provider issuance flow.")
let url = try runIdentityProviderFlow(identityProvider.metadata.issuanceStart, reqJSON)
print("Identity verification process started!")
return .init(url: url)
}

func todoOpenURL(_: URL) {
// Open the URL in a web view to start the identity verification flow with the identity provider.
fatalError("'openURL' not implemented")
}

func todoAwaitCallbackWithVerificationPollingURL() -> URL {
// Block the thread and wait for the callback URL to be invoked (and somehow capture that event).
// In mobile wallets, the callback URL is probably a deep link that we listen on somewhere else.
// In that case, this snippet would be done now and we would expect the handler to be eventually invoked.
// In either case, the callback is how the IP hands over the URL for polling the verification status -
// and for some reason it does so in the *fragment* part of the URL!
// See 'server.swift' of the example CLI for a server-based solution that works in a synchronous context.
// Warning: It ain't pretty.
fatalError("'awaitCallbackWithVerificationPollingURL' not implemented")
}

func todoFetchIdentityIssuance(_ request: IdentityIssuanceRequest) async throws -> IdentityVerificationResult {
// Block the thread, periodically polling for the verification status.
// Return the result once it's no longer "pending" (i.e. the result is non-nil).
while true {
let status = try await request.send(session: URLSession.shared)
if let r = status.result {
return r
}
try await Task.sleep(nanoseconds: 10 * 1_000_000_000) // check once every 10s
}
}

++++++++++++++++++++++++
Send an identity request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ Here is an example of how the list of identity providers can be retrieved from t

.. tab::

Swift (iOS)
Swift (macOS, iOS)

The Swift SDK for iOS is still in development.
.. code-block:: Swift

import Concordium
import Foundation

// Inputs.
let walletProxyBaseURL = URL(string: "https://wallet-proxy.testnet.concordium.com")!

let walletProxy = WalletProxy(baseURL: walletProxyBaseURL)
print("Identity providers:")
for ip in try await identityProviders(walletProxy) {
print("- \(ip.info.description.name)")
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,55 @@ The next step is to send the generated identity recovery request to the associat

.. tab::

Swift (iOS)
Swift (macOS, iOS)

.. code-block:: Swift

import Concordium
import Foundation

// Inputs.
let seedPhrase = "fence tongue sell large master side flock bronze ice accident what humble bring heart swear record valley party jar caution horn cushion endorse position"
let network = Network.testnet
let identityProviderID = IdentityProviderID(3)
let identityIndex = IdentityIndex(7)
let walletProxyBaseURL = URL(string: "https://wallet-proxy.testnet.concordium.com")!
let anonymityRevocationThreshold = RevocationThreshold(2)

/// Perform identity recovery based on the inputs above.
func recoverIdentity(client: NodeClient) async throws {
let seed = try decodeSeed(seedPhrase, network)
let walletProxy = WalletProxy(baseURL: walletProxyBaseURL)
let identityProvider = try await findIdentityProvider(walletProxy, identityProviderID)!

// Construct recovery request.
let cryptoParams = try await client.cryptographicParameters(block: .lastFinal)
let identityReq = try makeIdentityRecoveryRequest(seed, cryptoParams, identityProvider, identityIndex)

// Execute request.
let identity = try await identityReq.send(session: URLSession.shared)
print("Successfully recovered identity: \(identity)")
}

The Swift SDK for iOS is still in development.
// Duplicated in 'CreateAccount/main.swift'.
func makeIdentityRecoveryRequest(
_ seed: WalletSeed,
_ cryptoParams: CryptographicParameters,
_ identityProvider: IdentityProvider,
_ identityIndex: IdentityIndex
) throws -> IdentityRecoverRequest {
let identityRequestBuilder = SeedBasedIdentityRequestBuilder(
seed: seed,
cryptoParams: cryptoParams
)
let reqJSON = try identityRequestBuilder.recoveryRequestJSON(
provider: identityProvider.info,
index: identityIndex,
time: Date.now
)
let urlBuilder = IdentityRequestURLBuilder(callbackURL: nil)
return try urlBuilder.recoveryRequest(
baseURL: identityProvider.metadata.recoveryStart,
requestJSON: reqJSON
)
}
11 changes: 9 additions & 2 deletions source/mainnet/net/guides/wallet-sdk/wallet-sdk.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,16 @@ Below, you can find a link to the SDK package for your chosen technology, and an

.. tab::

Swift (iOS)
Swift (macOS, iOS)

The Swift SDK for iOS is still in development.
| Swift Package
| `concordium-swift-sdk <https://github.com/Concordium/concordium-swift-sdk>`

| Working example implementation of a CLI tool for macOS
| https://github.com/Concordium/concordium-swift-sdk/tree/main/examples/CLI

| Code snippets used in this documentation
| https://github.com/Concordium/concordium-swift-sdk/tree/main/examples/DocSnippets

.. toctree::
:hidden:
Expand Down
Loading