Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add CCIP reverse resolving #35

Merged
merged 13 commits into from
May 9, 2024
48,827 changes: 31,309 additions & 17,518 deletions packages/core/__mocks__/naming/naming.compiled_contract_class.json

Large diffs are not rendered by default.

22,155 changes: 10,552 additions & 11,603 deletions packages/core/__mocks__/naming/naming.contract_class.json

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions packages/core/__test__/profile_mainnet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ describe("test starknetid.js sdk on mainnet", () => {
"0x061b6c0a78f9edf13cea17b50719f3344533fadd470b8cb29c2b4318014f52d3",
false,
);

expect(
profile.profilePicture &&
profile.profilePicture.startsWith("https://img.starkurabu.com"),
Expand Down Expand Up @@ -179,7 +178,6 @@ describe("test starknetid.js sdk on mainnet", () => {
"0x0097095403155fcbFA72AA53270D6eDd0DCC830bBb9264455517DF3e508633E5", // nothing
"0x007b275f7524f39b99a51c7134bc44204fedc5dd1e982e920eb2047c6c2a71f0", // everai pfp
]);
console.log("profiles", profiles);
const expectedProfiles = [
{ name: "iris.stark" },
{ name: "rmz.stark" },
Expand Down
108 changes: 103 additions & 5 deletions packages/core/__test__/resolve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe("test starknetid.js sdk", () => {
jest.setTimeout(90000000);
const provider = getTestProvider();
const account = getTestAccount(provider)[0];
const account2 = getTestAccount(provider)[1];

let erc20Address: string =
"0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";
Expand Down Expand Up @@ -106,16 +107,18 @@ describe("test starknetid.js sdk", () => {
0,
],
},
{
contractAddress: IdentityContract,
entrypoint: "set_main_id",
calldata: ["1"],
},
// add uri to resolver contract
{
contractAddress: ResolverContract,
entrypoint: "add_uri",
calldata: [2, ...serverUri],
},
// set_domain_to_resolver
{
contractAddress: NamingContract,
entrypoint: "set_domain_to_resolver",
calldata: [1, 1068731, ResolverContract],
},
],
undefined,
{ maxFee: 1e18 },
Expand Down Expand Up @@ -189,4 +192,99 @@ describe("test starknetid.js sdk", () => {
).rejects.toThrow("Could not get address from stark name");
});
});

describe("Try reverse resolving a domain without set_address_to_domain ", () => {
beforeEach(() => {
fetch.mockClear();
});

test("resolve address returns an error", async () => {
fetch.mockResolvedValue({
ok: true,
json: async () => ({
address:
"0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691",
r: "0x7bdc9f102e7085464431ae1a89f1d1cc51abf0a1dfa3fba8016b05cb4365219",
s: "0x6d557890203c75df13d880691ac8af5323d0cb7c944d34fc271425f442eae9f",
max_validity: 1716966719,
}),
});
const starknetIdNavigator = new StarknetIdNavigator(
provider,
constants.StarknetChainId.SN_GOERLI,
{
naming: NamingContract,
identity: IdentityContract,
},
);
expect(starknetIdNavigator).toBeInstanceOf(StarknetIdNavigator);
await expect(
starknetIdNavigator.getStarkName(account.address),
).rejects.toThrow("Could not get stark name");
});
});

describe("Test reverse resolving", () => {
const serverResponse = {
address:
"0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691",
r: "0x7bdc9f102e7085464431ae1a89f1d1cc51abf0a1dfa3fba8016b05cb4365219",
s: "0x6d557890203c75df13d880691ac8af5323d0cb7c944d34fc271425f442eae9f",
max_validity: 1716966719,
};
beforeEach(() => {
fetch.mockClear();
});

beforeAll(async () => {
expect(account).toBeInstanceOf(Account);
const { transaction_hash } = await account.execute(
[
{
contractAddress: NamingContract,
entrypoint: "set_address_to_domain",
calldata: [
// iris.test.stark encoded
2,
999902,
1068731,
// hints
4,
serverResponse.address,
serverResponse.r,
serverResponse.s,
serverResponse.max_validity,
],
},
],
undefined,
{ maxFee: 1e18 },
);
await provider.waitForTransaction(transaction_hash);
});

test("resolve address returns the subdomain", async () => {
fetch.mockResolvedValue({
ok: true,
json: async () => ({
address:
"0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691",
r: "0x7bdc9f102e7085464431ae1a89f1d1cc51abf0a1dfa3fba8016b05cb4365219",
s: "0x6d557890203c75df13d880691ac8af5323d0cb7c944d34fc271425f442eae9f",
max_validity: 1716966719,
}),
});
const starknetIdNavigator = new StarknetIdNavigator(
provider,
constants.StarknetChainId.SN_GOERLI,
{
naming: NamingContract,
identity: IdentityContract,
},
);
expect(starknetIdNavigator).toBeInstanceOf(StarknetIdNavigator);
const address = await starknetIdNavigator.getStarkName(account.address);
expect(address).toBe("iris.test.stark");
});
});
});
27 changes: 27 additions & 0 deletions packages/core/__test__/resolve_sepolia.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Account, Provider, constants } from "starknet";
import { StarknetIdNavigator } from "../src";

describe("test starknetid.js sdk on sepolia", () => {
jest.setTimeout(90000000);
const provider = new Provider({
rpc: {
nodeUrl: "https://sepolia.rpc.starknet.id",
},
});

describe("Test offchain resolving demo", () => {
test("iris.notion.stark resolve to the right address", async () => {
const starknetIdNavigator = new StarknetIdNavigator(
provider,
constants.StarknetChainId.SN_SEPOLIA,
);
expect(starknetIdNavigator).toBeInstanceOf(StarknetIdNavigator);
const address = await starknetIdNavigator.getAddressFromStarkName(
"iris.notion.stark",
);
expect(address).toBe(
"0x220756d68c9b120fcfc539510fc474359bea9f8bc73e8af3a23a8276d571faf",
);
});
});
});
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "starknetid.js",
"version": "3.2.0",
"version": "3.2.1",
"keywords": [
"starknet",
"starknetid",
Expand Down
121 changes: 63 additions & 58 deletions packages/core/src/starknetIdNavigator/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
constants,
CallData,
Contract,
Call,
} from "starknet";
import {
decodeDomain,
Expand All @@ -25,8 +24,6 @@ import {
import { StarknetIdNavigatorInterface } from "./interface";
import { StarkProfile, StarknetIdContracts } from "../types";
import {
executeMulticallWithFallback,
executeWithFallback,
extractArrayFromErrorMessage,
fetchImageUrl,
getProfileDataCalldata,
Expand Down Expand Up @@ -103,39 +100,41 @@ export class StarknetIdNavigator implements StarknetIdNavigatorInterface {
this.StarknetIdContract.naming ?? getNamingContract(this.chainId);

try {
// todo: remove fallback when naming contract is updated on mainnet
const calldata: Call = {
contractAddress: contract,
entrypoint: "address_to_domain",
calldata: CallData.compile({
address: address,
hint: [],
}),
};
const fallbackCalldata: Call = {
contractAddress: contract,
entrypoint: "address_to_domain",
calldata: CallData.compile({
address: address,
}),
};
const hexDomain = await executeWithFallback(
this.provider,
calldata,
fallbackCalldata,
);
const decimalDomain = hexDomain.result
.map((element) => BigInt(element))
.slice(1);
const stringDomain = decodeDomain(decimalDomain);
return await this.tryResolveAddress(contract, address);
} catch (error) {
if (error instanceof Error) {
// extract server uri from error message
const data = extractArrayFromErrorMessage(String(error));
if (!data || data?.errorType !== "offchain_resolving") {
// if the error is not related to offchain resolving
throw new Error("Could not get stark name");
}

if (!stringDomain) {
// we try querying the server for each uri, and will stop once one is working
for (const uri of data.uris) {
try {
const serverRes = await queryServer(uri, data.domain_slice);
if (serverRes.error) {
continue;
}
// try resolving with hint
const hint: any[] = [
serverRes.data.address,
serverRes.data.r,
serverRes.data.s,
serverRes.data.max_validity,
];
return await this.tryResolveAddress(contract, address, hint);
} catch (error: any) {
throw new Error(
`Could not resolve domain on URI ${uri} : ${error.message}`,
);
}
}
throw new Error("Could not get stark name");
} else {
throw new Error("Could not get stark name");
}

return stringDomain;
} catch (e) {
throw new Error("Could not get stark name");
}
}

Expand All @@ -160,17 +159,8 @@ export class StarknetIdNavigator implements StarknetIdNavigatorInterface {
);

try {
let { initialCalldata, fallbackCalldata } = getStarknamesCalldata(
addresses,
namingContract,
);

const data = await executeMulticallWithFallback(
contract,
"aggregate",
initialCalldata,
fallbackCalldata,
);
let calldata = getStarknamesCalldata(addresses, namingContract);
const data = await contract.call("aggregate", [calldata]);

let result: string[] = [];
if (Array.isArray(data)) {
Expand Down Expand Up @@ -492,20 +482,15 @@ export class StarknetIdNavigator implements StarknetIdNavigatorInterface {
);

try {
const { initialCalldata, fallbackCalldata } = getProfileDataCalldata(
const calldata = getProfileDataCalldata(
address,
namingContract,
identityContract,
verifierContract,
pfpVerifierContract,
popVerifierContract,
);
const data = await executeMulticallWithFallback(
multicallContract,
"aggregate",
initialCalldata,
fallbackCalldata,
);
const data = await multicallContract.call("aggregate", [calldata]);

if (Array.isArray(data)) {
const name = decodeDomain(data[0].slice(1));
Expand Down Expand Up @@ -584,7 +569,7 @@ export class StarknetIdNavigator implements StarknetIdNavigatorInterface {

try {
const nbInstructions = 5;
const { initialCalldata, fallbackCalldata } = getStarkProfilesCalldata(
const calldata = getStarkProfilesCalldata(
addresses,
namingContract,
identityContract,
Expand All @@ -593,12 +578,7 @@ export class StarknetIdNavigator implements StarknetIdNavigatorInterface {
blobbertContract,
);

const data = await executeMulticallWithFallback(
multicallContract,
"aggregate",
initialCalldata,
fallbackCalldata,
);
const data = await multicallContract.call("aggregate", [calldata]);

if (Array.isArray(data)) {
let results: StarkProfile[] = [];
Expand Down Expand Up @@ -665,6 +645,31 @@ export class StarknetIdNavigator implements StarknetIdNavigatorInterface {
return addressData.result[0];
}

private async tryResolveAddress(
contract: string,
address: string,
hint: any = [],
): Promise<string> {
const domainData = await this.provider.callContract({
contractAddress: contract,
entrypoint: "address_to_domain",
calldata: CallData.compile({
address: address,
hint,
}),
});

const decimalDomain = domainData.result
.map((element) => BigInt(element))
.slice(1);
const stringDomain = decodeDomain(decimalDomain);

if (!stringDomain) {
throw new Error("Could not get stark name");
}
return stringDomain;
}

private async checkArguments(idDomainOrAddr: string): Promise<string> {
if (typeof idDomainOrAddr === "string") {
if (/^\d+$/.test(idDomainOrAddr)) {
Expand Down
Loading
Loading