Skip to content
This repository has been archived by the owner on Jul 15, 2022. It is now read-only.

Commit

Permalink
Add Celo coin with Sync and Send features (#1536)
Browse files Browse the repository at this point in the history
* Add Celo family with Sync and Send features

* Remove Operation types related to staking

* Add speculos bot specs

* Display an error if receipient address is invalid

* Add deviceTransactionConfig.ts to display send transaction confirmation data

* Add CELO svg icon

* Remove Method from transaction confirmation fields

* Refactoring - code review feedback

* Fix Celo Contractkit populate method bug, add send max spec

* Use erc20 for Celo token to fix precision issues

* Update Celo indexer/node API urls

* Use Celo Gold contract for fee estimations

* Fix estimating fee on sending max amount

* Fix linting
  • Loading branch information
pawelnguyen authored Jan 28, 2022
1 parent 0f104e9 commit b9b4d7d
Show file tree
Hide file tree
Showing 42 changed files with 3,473 additions and 137 deletions.
7 changes: 3 additions & 4 deletions cli/src/commands/generateTestScanAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,16 @@ export default {
if (accounts.length === 0) throw new Error("no accounts!");
const { currency } = accounts[0];
return `
// @flow
import type { CurrenciesData } from "../../../types";
import type { Transaction } from "../types";
const dataset: CurrenciesData<Transaction> = {
scanAccounts: [
{
name: "${currency.id} seed 1",
apdus: \`\n${apdus.map((a) => " " + a).join("\n")}\n \`
}
]
apdus: \`\n${apdus.map((a) => " " + a).join("\n")}\n \`,
},
],
};
export default dataset;
Expand Down
1 change: 1 addition & 0 deletions cli/src/live-common-setup-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ setSupportedCurrencies([
"cosmos_testnet",
"crypto_org",
"crypto_org_croeseid",
"celo",
]);

for (const k in process.env) setEnvUnsafe(k as EnvName, process.env[k]);
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
"https": false
},
"dependencies": {
"@celo/contractkit": "^1.5.1",
"@celo/wallet-base": "^1.5.1",
"@celo/wallet-ledger": "^1.5.1",
"@crypto-com/chain-jslib": "0.0.19",
"@ledgerhq/compressjs": "1.3.2",
"@ledgerhq/cryptoassets": "6.23.1",
Expand Down
2 changes: 1 addition & 1 deletion scripts/sync-families-dispatch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ presync.ts \
platformAdapter.ts \
"

withoutNetworkInfo=("algorand polkadot solana")
withoutNetworkInfo=("algorand polkadot solana celo")

cd ../src

Expand Down
1 change: 1 addition & 0 deletions src/__tests__/test-helpers/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ setSupportedCurrencies([
"crypto_org",
"filecoin",
"solana",
"celo",
]);
1 change: 1 addition & 0 deletions src/account/support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { getAccountBridge } from "../bridge";
import jsBridges from "../generated/bridge/js";

const experimentalIntegrations = ["algorand", "tezos"];

export function shouldUseJS(currency: CryptoCurrency) {
const jsBridge = jsBridges[currency.family];
if (!jsBridge) return false;
Expand Down
2 changes: 1 addition & 1 deletion src/bot/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ export async function runOnAccount<T extends Transaction>({
}
}

// without recovering mecanism, we simply assume an error is a failure
// without recovering mechanism, we simply assume an error is a failure
if (errors.length) {
throw errors[0];
}
Expand Down
2 changes: 1 addition & 1 deletion src/bridge/jsHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Operation[] {
newOps.forEach((op) => {
newOpsIds[op.id] = op;
});
// return existins when there is no real new operations
// return existing when there is no real new operations
if (newOps.length === 0) return existing;
// edge case, existing can be empty. return the sorted list.
if (existing.length === 0) return Object.values(newOpsIds);
Expand Down
5 changes: 5 additions & 0 deletions src/data/icons/svg/CELO.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ const envDefinitions = {
parser: stringParser,
desc: "Node API endpoint for algorand",
},
API_CELO_INDEXER: {
def: "https://celo.coin.ledger.com/indexer/",
parser: stringParser,
desc: "Explorer API for celo",
},
API_CELO_NODE: {
def: "https://celo.coin.ledger.com/archive/",
parser: stringParser,
desc: "Node endpoint for celo",
},
API_COSMOS_BLOCKCHAIN_EXPLORER_API_ENDPOINT: {
def: "https://cosmoshub4.coin.ledger.com/",
parser: stringParser,
Expand Down
93 changes: 93 additions & 0 deletions src/families/celo/api/hubble.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { BigNumber } from "bignumber.js";
import network from "../../../network";
import { getEnv } from "../../../env";
import { Operation, OperationType } from "../../../types";
import { encodeOperationId } from "../../../operation";

const DEFAULT_TRANSACTIONS_LIMIT = 200;
const getUrl = (route): string => `${getEnv("API_CELO_INDEXER")}${route || ""}`;

// Indexer returns both account data and transactions in one call.
// Transactions are just limited, there's no block height offset
const fetchAccountDetails = async (
address: string,
transactionsLimit: number = DEFAULT_TRANSACTIONS_LIMIT
) => {
const { data } = await network({
method: "GET",
url: getUrl(`/account_details/${address}?limit=${transactionsLimit}`),
});
return data;
};

const fetchStatus = async () => {
const { data } = await network({
method: "GET",
url: getUrl(`/status`),
});
return data;
};

const getOperationType = (type: string): OperationType => {
switch (type) {
case "InternalTransferSent":
return "OUT";
case "InternalTransferReceived":
return "IN";
case "AccountSlashed":
return "SLASH";
default:
return "NONE";
}
};

const transactionToOperation = (
address: string,
accountId: string,
transaction
): Operation => {
const type: OperationType = getOperationType(transaction.kind);
const hasFailed = transaction.data.success
? !transaction.data.success
: false;
const senders = transaction.data.from ? [transaction.data.from] : [];
const recipients = transaction.data.to ? [transaction.data.to] : [];

return {
id: encodeOperationId(accountId, transaction.transaction_hash, type),
hash: transaction.transaction_hash,
accountId,
fee: new BigNumber(0),
value: new BigNumber(transaction.amount),
type,
blockHeight: transaction.height,
date: new Date(transaction.time),
senders,
recipients,
hasFailed,
blockHash: null,
extra: {},
};
};

export const getAccountDetails = async (address: string, accountId: string) => {
const accountDetails = await fetchAccountDetails(address);
const spendableBalance = new BigNumber(accountDetails.gold_balance);
const lockedBalance = new BigNumber(accountDetails.total_locked_gold);
const balance = spendableBalance.plus(lockedBalance);
const indexerStatus = await fetchStatus();

const allTransactions = accountDetails.internal_transfers.concat(
accountDetails.transactions
);
const operations = allTransactions.map((transaction) =>
transactionToOperation(address, accountId, transaction)
);

return {
blockHeight: indexerStatus.last_indexed_height,
balance,
spendableBalance,
operations,
};
};
1 change: 1 addition & 0 deletions src/families/celo/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { getAccountDetails } from "./hubble";
8 changes: 8 additions & 0 deletions src/families/celo/api/sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ContractKit, newKit } from "@celo/contractkit";
import { getEnv } from "../../../env";

let kit: ContractKit;
export const celoKit = () => {
if (!kit) kit = newKit(getEnv("API_CELO_NODE"));
return kit;
};
38 changes: 38 additions & 0 deletions src/families/celo/bridge/js.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { AccountBridge, CurrencyBridge } from "../../../types";
import type { Transaction } from "../types";
import { makeAccountBridgeReceive } from "../../../bridge/jsHelpers";
import { sync, scanAccounts } from "../js-synchronisation";
import signOperation from "../js-signOperation";
import getTransactionStatus from "../js-getTransactionStatus";
import broadcast from "../js-broadcast";
import estimateMaxSpendable from "../js-estimateMaxSpendable";
import prepareTransaction from "../js-prepareTransaction";
import createTransaction from "../js-createTransaction";

const updateTransaction = (t, patch) => ({ ...t, ...patch });

const receive = makeAccountBridgeReceive();

const preload = async () => Promise.resolve({});
const hydrate = (): void => {};

const currencyBridge: CurrencyBridge = {
preload,
hydrate,
scanAccounts,
};
const accountBridge: AccountBridge<Transaction> = {
estimateMaxSpendable,
createTransaction,
updateTransaction,
getTransactionStatus,
prepareTransaction,
sync,
receive,
signOperation,
broadcast,
};
export default {
currencyBridge,
accountBridge,
};
41 changes: 41 additions & 0 deletions src/families/celo/cli-transaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import invariant from "invariant";
import flatMap from "lodash/flatMap";

import type {
Transaction,
Account,
AccountLike,
AccountLikeArray,
} from "../../types";

const options = [];

function inferAccounts(account: Account): AccountLikeArray {
invariant(account.currency.family === "celo", "celo family");

const accounts: Account[] = [account];
return accounts;
}

function inferTransactions(
transactions: Array<{
account: AccountLike;
transaction: Transaction;
mainAccount: Account;
}>
): Transaction[] {
return flatMap(transactions, ({ transaction }) => {
invariant(transaction.family === "celo", "celo family");

return {
...transaction,
family: "celo",
} as Transaction;
});
}

export default {
options,
inferAccounts,
inferTransactions,
};
20 changes: 20 additions & 0 deletions src/families/celo/datasets/celo.scanAccounts.1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { CurrenciesData } from "../../../types";
import type { Transaction } from "../types";

const dataset: CurrenciesData<Transaction> = {
scanAccounts: [
{
name: "celo seed 1",
apdus: `
=> e002000009028000002c8000ce10
<= 41040106794f46b69e1ae56dff904510959e59074a37341f8cc740e9ce7a0b24421c42e7023481f5ef03926739baa6bb26cac7bf48f249a24793264711d48499499528433235323935323541463035434134313939633236413838623438393463383342303330433733399000
=> e002000015058000002c8000ce10800000000000000000000000
<= 41044f4998c3f81966645f25e95082f9fe7d104b1ba4286486a8ecd8fd11f79ef02bd8110f086b390a418ab619a650f3df59a3c84912ce1ea125386d7aa7732fcd0b28393634313334336638346535304143304334434133626644313244433063346231653732363544359000
=> e002000015058000002c8000ce10800000010000000000000000
<= 4104630b6447b0f690520a5612f1dc57da90b84f6b59dfeb375bed1113fb34a8d0f9181ec63e40f9fde05761084dbd022ba76711eb2b36442cf4b5d2bc19def9156428343435306565646238623532333931354534393639633239646241653944354563303646453837349000
`,
},
],
};

export default dataset;
19 changes: 19 additions & 0 deletions src/families/celo/deviceTransactionConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { DeviceTransactionField } from "../../transaction";

function getDeviceTransactionConfig(): Array<DeviceTransactionField> {
const fields: Array<DeviceTransactionField> = [];

fields.push({
type: "amount",
label: "Amount",
});

fields.push({
type: "fees",
label: "Fees",
});

return fields;
}

export default getDeviceTransactionConfig;
Loading

1 comment on commit b9b4d7d

@vercel
Copy link

@vercel vercel bot commented on b9b4d7d Jan 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.