Skip to content

Commit

Permalink
Merge pull request #400 from 0xBenaparte/feat/add-starknet-id-getters
Browse files Browse the repository at this point in the history
feat: add starknet.id getters
  • Loading branch information
ivpavici authored Dec 2, 2022
2 parents 511548b + 81f5d71 commit d87bc20
Show file tree
Hide file tree
Showing 9 changed files with 98,248 additions and 3 deletions.
53,283 changes: 53,283 additions & 0 deletions __mocks__/naming_compiled.json

Large diffs are not rendered by default.

44,703 changes: 44,703 additions & 0 deletions __mocks__/starknetId_compiled.json

Large diffs are not rendered by default.

63 changes: 62 additions & 1 deletion __tests__/account.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { isBN } from 'bn.js';

import typedDataExample from '../__mocks__/typedDataExample.json';
import { Account, Contract, Provider, number, stark } from '../src';
import { Account, Contract, DeployContractPayload, Provider, number, stark } from '../src';
import { feeTransactionVersion } from '../src/utils/hash';
import { hexToDecimalString, toBN } from '../src/utils/number';
import { encodeShortString } from '../src/utils/shortString';
import { randomAddress } from '../src/utils/stark';
import {
compiledErc20,
compiledNamingContract,
compiledStarknetId,
compiledTestDapp,
erc20ClassHash,
getTestAccount,
Expand Down Expand Up @@ -164,6 +166,65 @@ describe('deploy and test Wallet', () => {

expect(declareTx.class_hash).toBeDefined();
});

test('Get the stark name of the account and account from stark name (using starknet.id)', async () => {
// Deploy naming contract
const namingPlayLoad: DeployContractPayload = { contract: compiledNamingContract };
const namingResponse = await provider.deployContract(namingPlayLoad);
const namingAddress = namingResponse.contract_address;

// Deploy Starknet id contract
const idPlayLoad: DeployContractPayload = { contract: compiledStarknetId };
const idResponse = await provider.deployContract(idPlayLoad);
const idAddress = idResponse.contract_address;

const { transaction_hash } = await account.execute([
{
contractAddress: namingAddress,
entrypoint: 'initializer',
calldata: [
idAddress, // starknetid_contract_addr
'0', // pricing_contract_addr
account.address, // admin
'1576987121283045618657875225183003300580199140020787494777499595331436496159', // whitelisting_key
'0', // l1_contract
],
},
{
contractAddress: idAddress,
entrypoint: 'mint',
calldata: ['1'], // TokenId
},
{
contractAddress: namingAddress,
entrypoint: 'whitelisted_mint',
calldata: [
'18925', // Domain encoded "ben"
'1697380617', // Expiry
'1', // Starknet id linked
account.address, // receiver_address
'1249449923402095645023546949816521361907869702415870903008894560968474148064', // sig 0 for whitelist
'543901326374961504443808953662149863005450004831659662383974986108355067943', // sig 1 for whitelist
],
},
{
contractAddress: namingAddress,
entrypoint: 'set_address_to_domain',
calldata: [
'1', // length
'18925', // Domain encoded "ben"
],
},
]);

await provider.waitForTransaction(transaction_hash);

const address = await account.getAddressFromStarkName('ben.stark', namingAddress);
expect(hexToDecimalString(address as string)).toEqual(hexToDecimalString(account.address));

const name = await account.getStarkName(namingAddress);
expect(name).toEqual('ben.stark');
});
});

describe('Declare and UDC Deploy Flow', () => {
Expand Down
2 changes: 2 additions & 0 deletions __tests__/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export const compiledL1L2 = readContract('l1l2_compiled');
export const compiledTypeTransformation = readContract('contract');
export const compiledMulticall = readContract('multicall');
export const compiledTestDapp = readContract('TestDapp');
export const compiledStarknetId = readContract('starknetId_compiled');
export const compiledNamingContract = readContract('naming_compiled');

/* Default test config based on run `starknet-devnet --seed 0` */
const DEFAULT_TEST_PROVIDER_SEQUENCER_URL = 'http://127.0.0.1:5050/';
Expand Down
38 changes: 38 additions & 0 deletions __tests__/utils/starknetId.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { StarknetChainId } from '../../src/constants';
import { getStarknetIdContract, useDecoded, useEncoded } from '../../src/utils/starknetId';

const characters = 'abcdefghijklmnopqrstuvwxyz0123456789-这来';

function generateString(length: number): string {
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < length; i += 1) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}

return result;
}

describe('Should tets StarknetId utils', () => {
test('Should test useEncoded and useDecoded hook with 100 random strings', () => {
for (let index = 0; index < 100; index += 1) {
const randomString = generateString(10);

expect(useDecoded([useEncoded(randomString)])).toBe(randomString.concat('.stark'));
}
});

test('Should test getStarknetIdContract', () => {
expect(getStarknetIdContract(StarknetChainId.TESTNET)).toBe(
'0x05cf267a0af6101667013fc6bd3f6c11116a14cda9b8c4b1198520d59f900b17'
);

expect(() => {
getStarknetIdContract(StarknetChainId.TESTNET2);
}).toThrow();

expect(getStarknetIdContract(StarknetChainId.MAINNET)).toBe(
'0x6ac597f8116f886fa1c97a23fa4e08299975ecaf6b598873ca6792b9bbfb678'
);
});
});
51 changes: 50 additions & 1 deletion src/account/default.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { BN } from 'bn.js';

import { UDC, ZERO } from '../constants';
import { ProviderInterface, ProviderOptions } from '../provider';
import { Provider } from '../provider/default';
Expand Down Expand Up @@ -29,9 +31,10 @@ import {
feeTransactionVersion,
transactionVersion,
} from '../utils/hash';
import { BigNumberish, toBN, toCairoBool } from '../utils/number';
import { BigNumberish, hexToDecimalString, toBN, toCairoBool } from '../utils/number';
import { parseContract } from '../utils/provider';
import { compileCalldata, estimatedFeeToMaxFee, randomAddress } from '../utils/stark';
import { getStarknetIdContract, useDecoded, useEncoded } from '../utils/starknetId';
import { fromCallsToExecuteCalldata } from '../utils/transaction';
import { TypedData, getMessageHash } from '../utils/typedData';
import { AccountInterface } from './interface';
Expand All @@ -56,6 +59,52 @@ export class Account extends Provider implements AccountInterface {
return super.getNonceForAddress(this.address, blockIdentifier);
}

public async getStarkName(StarknetIdContract?: string): Promise<string | Error> {
const chainId = await this.getChainId();
const contract = StarknetIdContract ?? getStarknetIdContract(chainId);

try {
const hexDomain = await this.callContract({
contractAddress: contract,
entrypoint: 'address_to_domain',
calldata: compileCalldata({
address: this.address,
}),
});
const decimalDomain = hexDomain.result
.map((element) => new BN(hexToDecimalString(element)))
.slice(1);

const stringDomain = useDecoded(decimalDomain);

return stringDomain;
} catch {
return Error('Could not get stark name');
}
}

public async getAddressFromStarkName(
name: string,
StarknetIdContract?: string
): Promise<string | Error> {
const chainId = await this.getChainId();
const contract = StarknetIdContract ?? getStarknetIdContract(chainId);

try {
const addressData = await this.callContract({
contractAddress: contract,
entrypoint: 'domain_to_address',
calldata: compileCalldata({
domain: [useEncoded(name.replace('.stark', '')).toString(10)],
}),
});

return addressData.result[0];
} catch {
return Error('Could not get address from stark name');
}
}

public async estimateFee(
calls: AllowArray<Call>,
estimateFeeDetails?: EstimateFeeDetails | undefined
Expand Down
2 changes: 1 addition & 1 deletion src/contract/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function parseFelt(candidate: string): BN {
try {
return toBN(candidate);
} catch (e) {
throw Error('Couldnt parse felt');
throw Error('Could not parse felt');
}
}

Expand Down
89 changes: 89 additions & 0 deletions src/utils/starknetId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* eslint-disable no-param-reassign */
import BN from 'bn.js';

import { StarknetChainId } from '../constants';

const basicAlphabet = 'abcdefghijklmnopqrstuvwxyz0123456789-';
const basicSizePlusOne = new BN(basicAlphabet.length + 1);
const bigAlphabet = '这来';
const basicAlphabetSize = new BN(basicAlphabet.length);
const bigAlphabetSize = new BN(bigAlphabet.length);
const bigAlphabetSizePlusOne = new BN(bigAlphabet.length + 1);

export function useDecoded(encoded: BN[]): string {
let decoded = '';
encoded.forEach((subdomain) => {
while (!subdomain.isZero()) {
const code = subdomain.mod(basicSizePlusOne).toNumber();
subdomain = subdomain.div(basicSizePlusOne);
if (code === basicAlphabet.length) {
const nextSubdomain = subdomain.div(bigAlphabetSizePlusOne);
if (nextSubdomain.isZero()) {
const code2 = subdomain.mod(bigAlphabetSizePlusOne).toNumber();
subdomain = nextSubdomain;
if (code2 === 0) decoded += basicAlphabet[0];
else decoded += bigAlphabet[code2 - 1];
} else {
const code2 = subdomain.mod(bigAlphabetSize).toNumber();
decoded += bigAlphabet[code2];
subdomain = subdomain.div(bigAlphabetSize);
}
} else decoded += basicAlphabet[code];
}
decoded += '.';
});

return decoded.concat('stark');
}

export function useEncoded(decoded: string): BN {
let encoded = new BN(0);
let multiplier = new BN(1);

for (let i = 0; i < decoded.length; i += 1) {
const char = decoded[i];
const index = basicAlphabet.indexOf(char);
const bnIndex = new BN(basicAlphabet.indexOf(char));

if (index !== -1) {
// add encoded + multiplier * index
if (i === decoded.length - 1 && decoded[i] === basicAlphabet[0]) {
encoded = encoded.add(multiplier.mul(basicAlphabetSize));
multiplier = multiplier.mul(basicSizePlusOne);
// add 0
multiplier = multiplier.mul(basicSizePlusOne);
} else {
encoded = encoded.add(multiplier.mul(bnIndex));
multiplier = multiplier.mul(basicSizePlusOne);
}
} else if (bigAlphabet.indexOf(char) !== -1) {
// add encoded + multiplier * (basicAlphabetSize)
encoded = encoded.add(multiplier.mul(basicAlphabetSize));
multiplier = multiplier.mul(basicSizePlusOne);
// add encoded + multiplier * index
const newid = (i === decoded.length - 1 ? 1 : 0) + bigAlphabet.indexOf(char);
encoded = encoded.add(multiplier.mul(new BN(newid)));
multiplier = multiplier.mul(bigAlphabetSize);
}
}

return encoded;
}

export function getStarknetIdContract(chainId: StarknetChainId): string {
const starknetIdMainnetContract =
'0x6ac597f8116f886fa1c97a23fa4e08299975ecaf6b598873ca6792b9bbfb678';
const starknetIdTestnetContract =
'0x05cf267a0af6101667013fc6bd3f6c11116a14cda9b8c4b1198520d59f900b17';

switch (chainId) {
case StarknetChainId.MAINNET:
return starknetIdMainnetContract;

case StarknetChainId.TESTNET:
return starknetIdTestnetContract;

default:
throw new Error('Starknet.id is not yet deployed on this network');
}
}
20 changes: 20 additions & 0 deletions www/docs/API/account.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,3 +417,23 @@ The _details_ object may include any of:

- details.**blockIdentifier**
- details.**nonce**

<hr />

account.**getStarkName**(StarknetIdContract) => _Promise<string | Error>_

Gets starknet.id stark name with the address of the account

The _StarknetIdContract_ argument can be undefined, if it is, the function will automatically use official starknet id contracts of your network (It currently supports TESTNET 1 only).

Returns directly a string (Example: `vitalik.stark`).

<hr />

account.**getAddressFromStarkName**(name, StarknetIdContract) => _Promise<string | Error>_

Gets account address with the starknet id stark name.

The _StarknetIdContract_ argument can be undefined, if it is, the function will automatically use official starknet id contracts of your network (It currently supports TESTNET 1 only).

Returns directly the address in a string (Example: `0xff...34`).

0 comments on commit d87bc20

Please sign in to comment.