Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@
.cxx
local.properties
.swiftpm
.kotlin
.kotlin
/.kotlin
/.kotlin/metadata
8 changes: 2 additions & 6 deletions flow/src/androidMain/kotlin/org/onflow/flow/crypto/Crypto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,7 @@ internal class SignerImpl(
Crypto.checkHashAlgoForSigning(hashAlgo)
}

override suspend fun sign(transaction: Transaction?, bytes: ByteArray): ByteArray {
TODO("Not yet implemented")
}

override suspend fun sign(bytes: ByteArray): ByteArray {
override suspend fun sign(bytes: ByteArray, transaction: Transaction?): ByteArray {
// check the private key is of the correct type
val ecSK = if (privateKey.privateKey is ECPrivateKey) {
privateKey.privateKey
Expand All @@ -252,4 +248,4 @@ internal class SignerImpl(
val curveOrderSize = Crypto.getCurveOrderSize(domain)
return Crypto.formatSignature(RS[0], RS[1], curveOrderSize)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ actual class ContractAddressRegister {
}

if (network != null) {
addresses[network] = contractAddresses.mapKeys { it.key.removePrefix("0x") }.toMutableMap()
addresses[network] = contractAddresses.mapKeys { it.key.removePrefix("0x").uppercase() }.toMutableMap()
} else {
println("Warning: Invalid network name: $networkStr")
}
Expand All @@ -58,7 +58,10 @@ actual class ContractAddressRegister {
}

actual fun getAddress(contract: String, network: ChainId): String? {
return addresses[network]?.get(contract)
val key = contract.removePrefix("0x")
val candidateKeys = listOf(key, key.uppercase(), key.lowercase())
val map = addresses[network] ?: return null
return candidateKeys.mapNotNull { map[it.uppercase()] ?: map[it] }.firstOrNull()
}

actual fun getAddresses(network: ChainId): Map<String, String> {
Expand All @@ -76,9 +79,11 @@ actual class ContractAddressRegister {
actual fun resolveImports(code: String, network: ChainId): String {
var result = code
getAddresses(network).forEach { (contract, address) ->
val pattern = "\\b0x${Regex.escape(contract)}\\b"
result = result.replace(Regex(pattern), address)
val normalized = contract.removePrefix("0x")
// Only replace 0x<Contract> placeholders (case-insensitive), avoid bare "evm" or "EVM.foo"
val pattern = Regex("\\b0x${Regex.escape(normalized)}\\b", RegexOption.IGNORE_CASE)
result = result.replace(pattern, address)
}
return result
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@
"0xFlowIDTableStaking": "0x8624b52f9ddcd04a",
"0xLockedTokens": "0x8d0e87b65159ae63"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import HybridCustody from 0xHYBRIDCUSTODY
import MetadataViews from 0xNONFUNGIBLETOKEN

access(all) fun main(parent: Address): {Address: AnyStruct} {
let acct = getAuthAccount<auth(Storage) &Account>(parent)
let m = acct.storage.borrow<&HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath)

if m == nil {
return {}
} else {
var data: {Address: AnyStruct} = {}
for address in m?.getChildAddresses()! {
let c = m?.getChildAccountDisplay(address: address)
data.insert(key: address, c)
}
return data
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import HybridCustody from 0xHYBRIDCUSTODY

access(all) fun main(parent: Address): [Address] {
let acct = getAuthAccount<auth(Storage) &Account>(parent)
if let manager = acct.storage.borrow<&HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath) {
return manager.getChildAddresses()
}
return []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import FungibleToken from 0xFUNGIBLETOKEN
import FlowToken from 0xFLOWTOKEN
import EVM from 0xEVM

/// Transfers $FLOW from the signer's account Cadence Flow balance to the recipient's hex-encoded EVM address.
/// Note that a COA must have a $FLOW balance in EVM before transferring value to another EVM address.
///
transaction(toEVMAddressHex: String, amount: UFix64, data: [UInt8], gasLimit: UInt64) {

let coa: auth(EVM.Withdraw, EVM.Call) &EVM.CadenceOwnedAccount
let recipientEVMAddress: EVM.EVMAddress

prepare(signer: auth(BorrowValue, SaveValue) &Account) {
if signer.storage.type(at: /storage/evm) == nil {
signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm)
}
self.coa = signer.storage.borrow<auth(EVM.Withdraw, EVM.Call) &EVM.CadenceOwnedAccount>(from: /storage/evm)
?? panic("Could not borrow reference to the signer's bridged account")

self.recipientEVMAddress = EVM.addressFromString(toEVMAddressHex)
}

execute {
if self.recipientEVMAddress.bytes == self.coa.address().bytes {
return
}
let valueBalance = EVM.Balance(attoflow: 0)
valueBalance.setFLOW(flow: amount)
let txResult = self.coa.call(
to: self.recipientEVMAddress,
data: data,
gasLimit: gasLimit,
value: valueBalance
)
assert(
txResult.status == EVM.Status.failed || txResult.status == EVM.Status.successful,
message: "evm_error=".concat(txResult.errorMessage).concat("\n")
)
}
}
19 changes: 19 additions & 0 deletions flow/src/androidMain/resources/scripts/common/evm/evm_run.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import FungibleToken from 0xFUNGIBLETOKEN
import FlowToken from 0xFLOWTOKEN
import EVM from 0xEVM

transaction(rlpEncodedTransaction: [UInt8], coinbaseAddr: String) {

prepare(signer: auth(Storage, EVM.Withdraw) &Account) {
let coinbase = EVM.addressFromString(coinbaseAddr)

let runResult = EVM.run(tx: rlpEncodedTransaction, coinbase: coinbase)
assert(
runResult.status == EVM.Status.successful,
message: "evm tx was not executed successfully."
)
}

execute {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import FlowStakingCollection from 0xFLOWTABLESTAKING
import FlowIDTableStaking from 0xFLOWTABLESTAKING
import LockedTokens from 0xLOCKEDTOKENS

access(all) fun main(address: Address): [FlowIDTableStaking.DelegatorInfo]? {
var res: [FlowIDTableStaking.DelegatorInfo]? = nil

let inited = FlowStakingCollection.doesAccountHaveStakingCollection(address: address)

if inited {
res = FlowStakingCollection.getAllDelegatorInfo(address: address)
}
return res
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import FungibleToken from 0xFUNGIBLETOKEN

/// Queries for FT.Vault balance of all FT.Vaults in the specified account.
///
access(all) fun main(address: Address): {String: UFix64} {
// Get the account
let account = getAuthAccount<auth(BorrowValue) &Account>(address)
// Init for return value
let balances: {String: UFix64} = {}
// Track seen Types in array
let seen: [String] = []
// Assign the type we'll need
let vaultType: Type = Type<@{FungibleToken.Vault}>()
// Iterate over all stored items & get the path if the type is what we're looking for
account.storage.forEachStored(fun (path: StoragePath, type: Type): Bool {
if !type.isRecovered && (type.isInstance(vaultType) || type.isSubtype(of: vaultType)) {
// Get a reference to the resource & its balance
let vaultRef = account.storage.borrow<&{FungibleToken.Balance}>(from: path)!
// Insert a new values if it's the first time we've seen the type
if !seen.contains(type.identifier) {
balances.insert(key: type.identifier, vaultRef.balance)
} else {
// Otherwise just update the balance of the vault (unlikely we'll see the same type twice in
// the same account, but we want to cover the case)
balances[type.identifier] = balances[type.identifier]! + vaultRef.balance
}
}
return true
})

// Add available Flow Token Balance
balances.insert(key: "availableFlowToken", account.availableBalance)

return balances
}
9 changes: 6 additions & 3 deletions flow/src/commonMain/kotlin/org/onflow/flow/AddressRegistry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ class AddressRegistry {
const val NFT_STOREFRONT = "0xNFTSTOREFRONT"
const val TOKEN_FORWARDING = "0xTOKENFORWARDING"
const val EVM = "0xEVM"
const val HYBRID_CUSTODY = "0xHYBRIDCUSTODY"
}

private val SCRIPT_TOKEN_MAP: MutableMap<String, MutableMap<String, FlowAddress>> = mutableMapOf()

var defaultChainId = ChainId.Mainnet
var defaultChainId: ChainIdProvider = ChainId.Mainnet

init {
registerDefaults()
Expand Down Expand Up @@ -84,7 +85,8 @@ class AddressRegistry {
STAKING_PROXY to FlowAddress("0x7aad92e5a0715d21"),
NON_FUNGIBLE_TOKEN to FlowAddress("0x631e88ae7f1d7c20"),
NFT_STOREFRONT to FlowAddress("0x94b06cfca1d8a476"),
EVM to FlowAddress("0x8c5303eaa26202d6")
EVM to FlowAddress("0x8c5303eaa26202d6"),
HYBRID_CUSTODY to FlowAddress("0x294e44e1ec6993c6")
),
ChainId.Mainnet to mutableMapOf(
FUNGIBLE_TOKEN to FlowAddress("0xf233dcee88fe0abe"),
Expand All @@ -96,7 +98,8 @@ class AddressRegistry {
NON_FUNGIBLE_TOKEN to FlowAddress("0x1d7e57aa55817448"),
NFT_STOREFRONT to FlowAddress("0x4eb8a10cb9f87357"),
TOKEN_FORWARDING to FlowAddress("0xe544175ee0461c4b"),
EVM to FlowAddress("0xe467b9dd11fa00df")
EVM to FlowAddress("0xe467b9dd11fa00df"),
HYBRID_CUSTODY to FlowAddress("0xd8a7e05a7ac670c0")
),
).forEach { chain ->
chain.value.forEach {
Expand Down
41 changes: 41 additions & 0 deletions flow/src/commonMain/kotlin/org/onflow/flow/CadenceTarget.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.onflow.flow

import io.ktor.util.decodeBase64Bytes
import org.onflow.flow.infrastructure.Cadence
import org.onflow.flow.models.Signer
import org.onflow.flow.models.Transaction

enum class CadenceTargetType {
Query,
Transaction
}

data class CadenceTarget(
val cadenceBase64: String,
val type: CadenceTargetType,
val arguments: List<Cadence.Value> = emptyList()
)

suspend fun FlowApi.query(target: CadenceTarget): Cadence.Value {
val script = target.cadenceBase64.decodeBase64Bytes().decodeToString()
return executeScript(script = script, arguments = target.arguments)
}

suspend fun FlowApi.sendTransaction(
target: CadenceTarget,
signers: List<Signer>,
chainId: ChainIdProvider = this.chainId,
addressRegistry: AddressRegistry = AddressRegistry(),
builder: TransactionDSLBuilder.() -> Unit
): Transaction {
val script = target.cadenceBase64.decodeBase64Bytes().decodeToString()
val combinedBuilder: TransactionDSLBuilder.() -> Unit = {
cadence { script }
arguments { target.arguments }
builder()
}

val unsignedTx = buildTransaction(chainId, addressRegistry, combinedBuilder)
val signedTx = signTransaction(unsignedTx, signers)
return sendTransaction(signedTx)
}
2 changes: 1 addition & 1 deletion flow/src/commonMain/kotlin/org/onflow/flow/FlowApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,4 @@ class FlowApi(val chainId: ChainIdProvider) {
suspend fun waitForSeal(transactionId: String): TransactionResult {
return transactionsApi.waitForSeal(transactionId)
}
}
}
Loading
Loading