Skip to content

Commit

Permalink
icp tokens integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Konstantinos Gaitanis committed Oct 8, 2024
1 parent 095b106 commit b3ff647
Show file tree
Hide file tree
Showing 17 changed files with 180 additions and 44 deletions.
4 changes: 4 additions & 0 deletions Sources/DAB/DABTokenService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ public class DABTokenService {
}
return try await transactionProvider.transactions(of: user, token: token)
}

public func explorerUrl(tokenCanister: ICPPrincipal, transactionIndex: String) async throws -> URL? {
try await transactionProvider.explorerUrl(tokenCanister: tokenCanister, transactionIndex: transactionIndex)
}
}

private extension DABTokenService {
Expand Down
2 changes: 1 addition & 1 deletion Sources/DAB/Token/Actors/ICRC1TokenActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ private extension ICRC1.TransferArgs {
init(_ args: ICPTokenTransferArgs) {
to = .init(owner: args.to.principal, subaccount: args.to.subAccountId)
fee = args.fee
memo = args.memo.map { Data($0.utf8) }
memo = args.memo
from_subaccount = args.from.subAccountId
created_at_time = args.createdAtTime?.nanoSecondsSince1970
amount = args.amount
Expand Down
41 changes: 41 additions & 0 deletions Sources/DAB/Token/DidFiles/icrc1_oracle.did
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
type Category = variant {
Sns;
Known;
Spam;
ChainFusionTestnet;
ChainFusion;
Community;
Native;
};

type Conf = record {
controllers : opt vec principal;
im_canister : opt principal;
};

type ICRC1 = record {
logo : opt text;
name : text;
ledger : text;
category : Category;
index : opt text;
symbol : text;
decimals : nat8;
fee : nat;
};

type ICRC1Request = record {
logo : opt text;
name : text;
ledger : text;
index : opt text;
symbol : text;
decimals : nat8;
fee : nat;
};service : (opt Conf) -> {
get_all_icrc1_canisters : () -> (vec ICRC1) query;
replace_icrc1_canisters : (vec ICRC1) -> ();
store_new_icrc1_canisters : (vec ICRC1) -> ();
store_icrc1_canister : (ICRC1Request) -> ();
sync_controllers: () -> (vec text);
}
4 changes: 2 additions & 2 deletions Sources/DAB/Token/ICPTokenActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ public struct ICPTokenTransferArgs {

// options
public let fee: BigUInt?
public let memo: String?
public let memo: Data?
public let createdAtTime: Date?

public init(sender: ICPSigningPrincipal, from: ICPAccount, to: ICPAccount, amount: BigUInt, fee: BigUInt?, memo: String?, createdAtTime: Date?) {
public init(sender: ICPSigningPrincipal, from: ICPAccount, to: ICPAccount, amount: BigUInt, fee: BigUInt?, memo: Data?, createdAtTime: Date?) {
self.sender = sender
self.from = from
self.to = to
Expand Down
9 changes: 6 additions & 3 deletions Sources/DAB/Token/ICPTokenTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public struct ICPTokenTransaction {
public var address: String {
switch self {
case .accountId(let address): return address
case .account(let icpAccount): return icpAccount.address
case .account(let icpAccount): return icpAccount.textualRepresentation()
}
}
}
Expand All @@ -31,7 +31,9 @@ public struct ICPTokenTransaction {
/// The block height
public let index: BigUInt
public let operation: Operation
public let memo: Data?
/// only available for ICP transactions
public let memo: UInt64?
public let icrc1Memo: Data?
public let amount: BigUInt
public let fee: BigUInt
public let created: Date?
Expand All @@ -58,10 +60,11 @@ public struct ICPTokenTransaction {
}
}

public init(index: BigUInt, operation: Operation, memo: Data?, amount: BigUInt, fee: BigUInt, created: Date?, timeStamp: Date?, spender: Destination?, token: ICPToken) {
public init(index: BigUInt, operation: Operation, memo: UInt64?, icrc1Memo: Data?, amount: BigUInt, fee: BigUInt, created: Date?, timeStamp: Date?, spender: Destination?, token: ICPToken) {
self.index = index
self.operation = operation
self.memo = memo
self.icrc1Memo = icrc1Memo
self.amount = amount
self.fee = fee
self.created = created
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private extension ICPTokenTransaction {
fee = .zero
spender = try burn.spender.map(ICPTokenTransaction.Destination.init)
created = burn.created_at_time.map { Date(nanoSecondsSince1970: $0) }
icrc1Memo = burn.memo

} else if let approve = transaction.transaction.approve {
operation = .approve(
Expand All @@ -59,20 +60,23 @@ private extension ICPTokenTransaction {
fee = approve.fee ?? .zero
spender = try ICPTokenTransaction.Destination(approve.spender)
created = approve.created_at_time.map { Date(nanoSecondsSince1970: $0) }
icrc1Memo = approve.memo

} else if let transfer = transaction.transaction.transfer {
operation = .transfer(from: try ICPTokenTransaction.Destination(transfer.from), to: try ICPTokenTransaction.Destination(transfer.to))
amount = transfer.amount
fee = transfer.fee ?? .zero
spender = try transfer.spender.map(ICPTokenTransaction.Destination.init)
created = transfer.created_at_time.map { Date(nanoSecondsSince1970: $0) }
icrc1Memo = transfer.memo

} else if let mint = transaction.transaction.mint {
operation = .mint(to: try ICPTokenTransaction.Destination(mint.to))
amount = mint.amount
fee = .zero
spender = nil
created = mint.created_at_time.map { Date(nanoSecondsSince1970: $0) }
icrc1Memo = mint.memo

} else {
return nil
Expand Down
3 changes: 2 additions & 1 deletion Sources/DAB/Token/Index/ICPIndexTransactionProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ private extension ICPTokenTransaction {
created = transaction.transaction.created_at_time.map { Date(nanoSecondsSince1970: $0.timestamp_nanos) }
index = BigUInt(transaction.id)
self.token = token
memo = transaction.transaction.icrc1_memo
icrc1Memo = transaction.transaction.icrc1_memo
memo = transaction.transaction.memo
}
}

Expand Down
17 changes: 13 additions & 4 deletions Sources/DAB/Token/Index/ICPTransactionProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,33 @@ class ICPTransactionProvider {
return try await provider.transactions(of: user)
}

func explorerUrl(tokenCanister: ICPPrincipal, transactionIndex: String) async throws -> URL? {
if tokenCanister == ICPSystemCanisters.ledger {
return URL(string: "https://dashboard.internetcomputer.org/transaction/\(transactionIndex)")
}
guard let root = try await findSNS(containing: tokenCanister)?.root_canister_id else {
return nil
}
return URL(string: "https://dashboard.internetcomputer.org/sns/\(root)/transaction/\(transactionIndex)")
}

private func deployedSnses() async throws -> [NNS_SNS_W.DeployedSns] {
if let cachedSnses = cachedSnses { return cachedSnses }
cachedSnses = try await service.list_deployed_snses().instances
return cachedSnses!
}

private func findIndexCanisterInSNS(tokenCanister: ICPPrincipal) async throws -> ICPPrincipal? {
private func findSNS(containing canister: ICPPrincipal) async throws -> NNS_SNS_W.DeployedSns? {
let deployed = try await deployedSnses()
let sns = deployed.first { $0.contains(tokenCanister) }
return sns?.index_canister_id
return deployed.first { $0.contains(canister) }
}

private func transactionProvider(for token: ICPToken) async throws -> ICPTransactionProviderProtocol? {
// TODO: Support DIP20 tokens
if token.canister == ICPSystemCanisters.ledger {
return ICPIndexTransactionProvider(client: service.client, icpToken: token)
}
guard let index = try await findIndexCanisterInSNS(tokenCanister: token.canister) else {
guard let index = try await findSNS(containing: token.canister)?.index_canister_id else {
return nil
}
return ICPICRC1IndexTransactionProvider(client: service.client, token: token, indexCanister: index)
Expand Down
7 changes: 5 additions & 2 deletions Sources/IcpKit/Canisters/ICPLedgerCanister.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,11 @@ private extension ICPBlock {
private extension ICPBlock.Transaction.Operation {
init(_ operation: LedgerCanister.Operation) {
switch operation {
case .Burn(let from, let amount): self = .burn(from: from, amount: amount.e8s)
case .Approve(let from, let allowance, let fee, let expires_at, let expected_allowance, let spender):
self = .approve(from: from, allowance: allowance.e8s, expectedAllowance: expected_allowance?.e8s, fee: fee.e8s, expiresAt: expires_at?.date, spender: spender)
case .Burn(let from, let amount, let spender): self = .burn(from: from, amount: amount.e8s, spender: spender)
case .Mint(let to, let amount): self = .mint(to: to, amount: amount.e8s)
case .Transfer(let to, let fee, let from, let amount): self = .transfer(from: from, to: to, amount: amount.e8s, fee: fee.e8s)
case .Transfer(let to, let fee, let from, let amount, let spender): self = .transfer(from: from, to: to, amount: amount.e8s, fee: fee.e8s, spender: spender)
}
}
}
Expand All @@ -155,4 +157,5 @@ private extension LedgerCanister.TimeStamp {
static var now: LedgerCanister.TimeStamp {
return LedgerCanister.TimeStamp(timestamp_nanos: UInt64(Date.now.timeIntervalSince1970) * 1_000_000_000)
}
var date: Date { Date(nanoSecondsSince1970: timestamp_nanos) }
}
26 changes: 24 additions & 2 deletions Sources/IcpKit/Canisters/LedgerCanister.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,34 +99,55 @@ enum LedgerCanister {

/// //There are three types of operations: minting tokens, burning tokens & transferring tokens
/// type Operation = variant {
/// Approve : record {
/// fee : Tokens;
/// from : AccountIdentifier;
/// allowance : Tokens;
/// expires_at : opt TimeStamp;
/// spender : AccountIdentifier;
/// expected_allowance : opt Tokens;
/// };
/// Mint: record {
/// to: AccountIdentifier;
/// amount: Tokens;
/// };
/// Burn: record {
/// from: AccountIdentifier;
/// amount: Tokens;
/// spender : opt AccountIdentifier;
/// };
/// Transfer: record {
/// from: AccountIdentifier;
/// to: AccountIdentifier;
/// amount: Tokens;
/// fee: Tokens;
/// spender : opt AccountIdentifier;
/// };
/// };
enum Operation: Codable {
case Burn(from: AccountIdentifier, amount: Tokens)
case Approve(from: AccountIdentifier, allowance: Tokens, fee: Tokens, expires_at: TimeStamp?, expected_allowance: Tokens?, spender: AccountIdentifier)
case Burn(from: AccountIdentifier, amount: Tokens, spender: AccountIdentifier?)
case Mint(to: AccountIdentifier, amount: Tokens)
case Transfer(to: AccountIdentifier, fee: Tokens, from: AccountIdentifier, amount: Tokens)
case Transfer(to: AccountIdentifier, fee: Tokens, from: AccountIdentifier, amount: Tokens, spender: AccountIdentifier?)

enum CodingKeys: String, CandidCodingKey {
case Approve
case Burn
case Mint
case Transfer
}
enum ApproveCodingKeys: String, CandidCodingKey {
case from
case allowance
case fee
case expires_at
case expected_allowance
case spender
}
enum BurnCodingKeys: String, CandidCodingKey {
case from
case amount
case spender
}
enum MintCodingKeys: String, CandidCodingKey {
case to
Expand All @@ -137,6 +158,7 @@ enum LedgerCanister {
case fee
case from
case amount
case spender
}
}

Expand Down
21 changes: 18 additions & 3 deletions Sources/IcpKit/Cryptography/ICPBlock.Transaction+Hash.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ public extension ICPCryptography {
let txHash = Cryptography.sha256(serialised)
return txHash
}

static func transactionHash(_ operation: ICPBlock.Transaction.Operation, memo: UInt64, createdNanos: UInt64) throws -> Data {
try transactionHash(ICPBlock.Transaction(memo: memo, createdNanos: createdNanos, operation: operation))
}
}

private extension ICPBlock.Transaction {
Expand All @@ -29,21 +33,21 @@ private extension ICPBlock.Transaction {
private extension ICPBlock.Transaction.Operation {
var cbor: CBOR {
switch self {
case .burn(from: let from, amount: let amount):
case .burn(let from, let amount, _):
return [
0: [
0: CBOR(from.hex),
1: CBOR(amount),
]
]
case .mint(to: let to, amount: let amount):
case .mint(let to, let amount):
return [
1: [
0: CBOR(to.hex),
1: CBOR(amount),
]
]
case .transfer(from: let from, to: let to, amount: let amount, fee: let fee):
case .transfer(let from, let to, let amount, let fee, _):
return [
2: [
0: CBOR(from.hex),
Expand All @@ -52,6 +56,17 @@ private extension ICPBlock.Transaction.Operation {
3: [0: CBOR(fee)],
]
]
case .approve(let from, let allowance, _, let fee, _, let spender):
return nil
// TODO: Can not find any docs for this...
// return [
// 3: [
// 0: CBOR(from.hex),
// 1: CBOR(spender.hex),
// 2: [0: CBOR(allowance)],
// 3: [0: CBOR(fee)],
// ]
// ]
}
}
}
2 changes: 1 addition & 1 deletion Sources/IcpKit/Models/ICPAccount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation
import Candid

public struct ICPAccount {
public struct ICPAccount: Equatable {
public let principal: ICPPrincipal
public var address: String { accountId.hex }
public let accountId: Data
Expand Down
17 changes: 10 additions & 7 deletions Sources/IcpKit/Models/ICPBlock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,23 @@ public struct ICPBlock {
public let operation: Operation

public enum Operation {
case burn(from: Data, amount: UInt64)
case approve(from: Data, allowance: UInt64, expectedAllowance: UInt64?, fee: UInt64, expiresAt: Date?, spender: Data)
case burn(from: Data, amount: UInt64, spender: Data?)
case mint(to: Data, amount: UInt64)
case transfer(from: Data, to: Data, amount: UInt64, fee: UInt64)
case transfer(from: Data, to: Data, amount: UInt64, fee: UInt64, spender: Data?)

public var amount: UInt64 {
switch self {
case .burn(_, let amount),
case .approve(_, let amount, _, _, _, _),
.burn(_, let amount, _),
.mint(_, let amount),
.transfer(_,_, let amount, _):
.transfer(_,_, let amount, _, _):
return amount
}
}

public var fee: UInt64? {
guard case .transfer(_,_,_, let fee) = self else { return nil }
guard case .transfer(_,_,_, let fee, _) = self else { return nil }
return fee
}
}
Expand All @@ -53,9 +55,10 @@ public struct ICPBlock {
private extension ICPTransactionType {
static func from(_ operation: ICPBlock.Transaction.Operation) -> ICPTransactionType {
switch operation {
case .burn(let from, _): return .burn(from: from.hex)
case .approve(let from, _, _, _, _, _): return .approve(from: from.hex)
case .burn(let from, _, _): return .burn(from: from.hex)
case .mint(let to, _): return .mint(to: to.hex)
case .transfer(let from, let to, _, _): return .send(from: from.hex, to: to.hex)
case .transfer(let from, let to, _, _, _): return .send(from: from.hex, to: to.hex)
}
}
}
Loading

0 comments on commit b3ff647

Please sign in to comment.