Skip to content

Commit

Permalink
Merge pull request #60 from ptisserand/wip-erc20
Browse files Browse the repository at this point in the history
Contract base class and ERC20 class implementations
  • Loading branch information
ptisserand committed Nov 22, 2022
2 parents 6d9d5eb + 40d219c commit ed03b05
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 1 deletion.
59 changes: 59 additions & 0 deletions example/examples/erc20.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'package:starknet/starknet.dart';

final privateKey = Felt.fromInt(1234);

final accountAddress = Felt.fromHexString(
"0x32d5c7a7953996056caf92ff4dd83f01ad72a3c418c05f15eb2f472d1e9c9f2");

final erc20Address = Felt.fromHexString(
"0x4e76f8708774c8162fb4da7abefb3cae94cc51cf3f9b40e0d44f24aabf8a521");

final myWalletAddress = Felt.fromHexString(
"0x0367c0c4603a29Bc5aCA8E07C6A2776D7C0d325945aBB4f772f448b345Ca4Cf7");

void main() async {
final provider = JsonRpcProvider(nodeUri: infuraGoerliTestnetUri);

final signer = Signer(privateKey: privateKey);

final account = Account(
provider: provider,
signer: signer,
accountAddress: accountAddress,
chainId: StarknetChainId.testNet);

final erc20 = ERC20(account: account, address: erc20Address);

Future<Uint256> account_balance(Felt account) async {
final balance = await erc20.balanceOf(account);
print('Balance of ${account.toHexString()}: $balance');
return balance;
}

final name = await erc20.name();
print('Name: $name');

final symbol = await erc20.symbol();
print('Symbol: $symbol');

final supply = await erc20.totalSupply();
print('Supply: $supply');

await account_balance(myWalletAddress);
await account_balance(accountAddress);

final allowance = await erc20.allowance(accountAddress, myWalletAddress);
print('Allowance: $allowance');

var trx = await erc20.transfer(
myWalletAddress,
Uint256(low: Felt.fromInt(1), high: Felt.fromInt(0)),
);
print('Transfer Transaction: $trx');
// wait for transaction ....
trx = await erc20.approve(
myWalletAddress,
Uint256(low: Felt.fromInt(2), high: Felt.fromInt(0)),
);
print('Approve transaction: $trx');
}
82 changes: 82 additions & 0 deletions lib/src/contract/contract.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'package:starknet/starknet.dart';

class Contract {
final Account account;
final Felt address;

Contract({required this.account, required this.address});

Future call(String selector, List<Felt> calldata) async {
final response = await account.provider.call(
request: FunctionCall(
contractAddress: address,
entryPointSelector: getSelectorByName(selector),
calldata: calldata,
),
blockId: BlockId.blockTag("latest"),
);
return (response.when(
error: (error) {
print('Error: $error');
return null;
},
result: (result) {
return result;
},
));
}

/// Get Nonce from account contract
Future<Felt> getNonce() async {
final response = await account.provider.call(
request: FunctionCall(
contractAddress: account.accountAddress,
entryPointSelector: getSelectorByName("get_nonce"),
calldata: [],
),
blockId: BlockId.blockTag("latest"),
);
return (response.when(error: (error) async {
if (error.code == 21 && error.message == "Invalid message selector") {
// Fallback on provider getNonce
final nonceResp =
await account.provider.getNonce(account.accountAddress);
return (nonceResp.when(
error: (error) {
throw Exception(
"Error provider getNonce (${error.code}): ${error.message}");
},
result: ((result) {
return result;
}),
));
} else {
throw Exception(
"Error call get_nonce (${error.code}): ${error.message}");
}
}, result: ((result) {
return result[0];
})));
}

Future<InvokeTransaction> execute(
String selector, List<Felt> calldata) async {
final Felt nonce = await getNonce();
final Felt maxFee = defaultMaxFee;
final Felt version = defaultVersion;

final trx = await account.execute(
functionCalls: [
FunctionCall(
contractAddress: address,
entryPointSelector: getSelectorByName(selector),
calldata: calldata,
),
],
nonce: nonce,
maxFee: maxFee,
version: version,
);
return trx;
}
}
105 changes: 105 additions & 0 deletions lib/src/contract/erc20.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import 'package:starknet/starknet.dart';

class ERC20 extends Contract {
ERC20({required super.account, required super.address});

/// Returns the name of the token.
Future<String> name() async {
final res = await call("name", []);
final Felt name = res[0];
return name.toSymbol();
}

/// Returns the symbol of the token, usually a shorter version of the name.
Future<String> symbol() async {
final res = await call("symbol", []);
final Felt symbol = res[0];
return symbol.toSymbol();
}

/// Returns the number of decimals used to get its user representation.
///
/// For example, if decimals equals 2, a balance of 505 tokens
/// should be displayed to a user as 5,05 (505 / 10 ** 2).
Future<Felt> decimals() async {
final res = await call("decimals", []);
return res;
}

/// Returns the amount of tokens in existence.
Future<Uint256> totalSupply() async {
final res = await call("totalSupply", []);
return Uint256(low: res[0], high: res[1]);
}

/// Returns the amount of tokens owned by `account`.
Future<Uint256> balanceOf(Felt account) async {
final res = await call("balanceOf", [account]);
return Uint256(low: res[0], high: res[1]);
}

/// Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner through transferFrom.
///
/// This is zero by default.
///
/// This value changes when approve or transferFrom are called.
Future<Uint256> allowance(Felt owner, Felt spender) async {
final res = await call("allowance", [owner, spender]);
return Uint256(low: res[0], high: res[1]);
}

/// Moves `amount` tokens from the caller’s account to `recipient`.
///
/// Returns transaction hash.
Future<String> transfer(Felt recipient, Uint256 value) async {
final InvokeTransaction trx = await execute(
"transfer",
[recipient, value.low, value.high],
);
return (trx.when(
result: (result) {
return result.transaction_hash;
},
error: (error) {
throw Exception("Error transfer (${error.code}): ${error.message}");
},
));
}

/// Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism.
/// amount is then deducted from the caller’s allowance.
///
/// Returns transaction hash.
Future<String> transferFrom(Felt from, Felt to, Uint256 value) async {
final InvokeTransaction trx = await execute(
"transferFrom",
[from, to, value.low, value.high],
);
return (trx.when(
result: (result) {
return result.transaction_hash;
},
error: (error) {
throw Exception("Error transferFrom (${error.code}): ${error.message}");
},
));
}

/// Sets `amount` as the allowance of `spender` over the caller’s tokens.
///
/// Returns transaction hash.
Future<String> approve(Felt spender, Uint256 amount) async {
final InvokeTransaction trx = await execute(
"approve",
[spender, amount.low, amount.high],
);
return (trx.when(
result: (result) {
return result.transaction_hash;
},
error: (error) {
throw Exception("Error transfer (${error.code}): ${error.message}");
},
));
}
}
2 changes: 2 additions & 0 deletions lib/src/contract/index.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'contract.dart';
export 'erc20.dart';
25 changes: 25 additions & 0 deletions lib/src/felt.dart → lib/src/types/felt.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:starknet/starknet.dart';
Expand Down Expand Up @@ -43,6 +44,10 @@ class Felt {
return bigIntToHexString(_bigInt);
}

String toHexString() {
return bigIntToHexString(_bigInt);
}

@override
String toString() {
return "Felt(${_bigInt.toString()})";
Expand All @@ -59,4 +64,24 @@ class Felt {

@override
int get hashCode => _bigInt.hashCode;

Uint8List _bigIntToUint8List(BigInt bigInt) =>
_bigIntToByteData(bigInt).buffer.asUint8List();

ByteData _bigIntToByteData(BigInt bigInt) {
final data = ByteData((bigInt.bitLength / 8).ceil());
var val = bigInt;

for (var i = 1; i <= data.lengthInBytes; i++) {
data.setUint8(data.lengthInBytes - i, val.toUnsigned(8).toInt());
val = val >> 8;
}

return data;
}

/// Interprets felt as a string
String toSymbol() {
return Utf8Codec().decode(_bigIntToUint8List(_bigInt));
}
}
2 changes: 2 additions & 0 deletions lib/src/types/index.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'felt.dart';
export 'uint256.dart';
17 changes: 17 additions & 0 deletions lib/src/types/uint256.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:starknet/starknet.dart';

class Uint256 {
final Felt low; // low 128 bits
final Felt high; // high 128 bits

Uint256({required this.low, required this.high});

BigInt toBigInt() {
return (high.toBigInt() << 128) + low.toBigInt();
}

@override
String toString() {
return toBigInt().toString();
}
}
3 changes: 2 additions & 1 deletion lib/starknet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export 'src/provider/index.dart';
export 'src/signer.dart';
export 'src/account.dart';
export 'src/static_config.dart';
export 'src/felt.dart';
export 'src/types/index.dart';
export 'src/convert.dart';
export 'src/util.dart';
export 'src/contract/index.dart';

0 comments on commit ed03b05

Please sign in to comment.