Skip to content

Commit

Permalink
feat(fees): merge fees
Browse files Browse the repository at this point in the history
  • Loading branch information
gershon committed Sep 10, 2024
1 parent 2083606 commit 9219328
Show file tree
Hide file tree
Showing 53 changed files with 2,571 additions and 278 deletions.
1 change: 1 addition & 0 deletions crates/ark-metadata/src/metadata_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ impl<'a, T: Storage, C: StarknetClient, F: FileManager, E: ElasticsearchManager>
ipfs_gateway_uri,
image_timeout,
request_referrer,
contract_address,
)
.await
.map_err(|err| MetadataError::RequestTokenUriError(err.to_string()))?;
Expand Down
48 changes: 38 additions & 10 deletions crates/ark-metadata/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,31 @@ use std::str::FromStr;
use std::time::Duration;
use tracing::{debug, error, trace, warn};

pub fn normalize_onchain_data(contract_address: &str, uri: &str) -> String {
let mut normalized = uri.replace("https://gateway.pinata.cloud/ipfs/", "ipfs://");

// This is a workaround to handle the invalid JSON format specific to the Loot Survivor: Beasts collection.
// The original JSON contains some improperly formatted quotes that need correction
// for proper parsing. This normalization is tailored specifically to fix those issues.

// Loot Survivor: Beasts
if contract_address == "0x0158160018d590d93528995b340260e65aedd76d28a686e9daa5c4e8fad0c5dd" {
normalized = normalized.replace("%20", " ").replace(":\"\"", ":\"");
normalized = ('A'..='Z').fold(normalized, |acc, c| {
acc.replace(&format!("\" {}", c), &format!(" {}", c))
});
}

normalized
}

pub async fn get_token_metadata(
client: &Client,
uri: &str,
ipfs_gateway_uri: &str,
request_timeout_duration: Duration,
request_referrer: &str,
contract_address: &str,
) -> Result<TokenMetadata> {
let parsed_uri = uri.replace("https://gateway.pinata.cloud/ipfs/", "ipfs://");
let metadata_type = get_metadata_type(parsed_uri.as_str());
Expand All @@ -40,7 +59,7 @@ pub async fn get_token_metadata(
}
MetadataType::OnChain(uri) => {
trace!("Fetching on-chain metadata: {}", uri);
fetch_onchain_metadata(&uri)?
fetch_onchain_metadata(contract_address, &uri)?
}
};
Ok(metadata)
Expand Down Expand Up @@ -171,8 +190,10 @@ pub fn get_content_type_from_extension(extension: &str) -> &str {
}
}

fn fetch_onchain_metadata(uri: &str) -> Result<TokenMetadata> {
let uri_string = urlencoding::decode(uri)
fn fetch_onchain_metadata(contract_address: &str, uri: &str) -> Result<TokenMetadata> {
let parsed_uri = normalize_onchain_data(contract_address, uri);

let uri_string = urlencoding::decode(parsed_uri.as_str())
.map(|s| s.into_owned())
.unwrap_or_else(|_| uri.to_string());

Expand Down Expand Up @@ -451,8 +472,8 @@ mod tests {
.to_string();
let encoded_metadata = base64_encode(&metadata_json);
let uri = format!("data:application/json;base64,{}", encoded_metadata);

let fetched_metadata = fetch_onchain_metadata(&uri).unwrap();
let contract_address = "0x1234567890123456789012345678901234567890";
let fetched_metadata = fetch_onchain_metadata(contract_address, &uri).unwrap();

assert_eq!(fetched_metadata.raw, metadata_json);
assert_eq!(
Expand All @@ -471,8 +492,8 @@ mod tests {
})
.to_string();
let uri = format!("data:application/json,{}", metadata_json);

let fetched_metadata = fetch_onchain_metadata(&uri).unwrap();
let contract_address = "0x1234567890123456789012345678901234567890";
let fetched_metadata = fetch_onchain_metadata(contract_address, &uri).unwrap();

assert_eq!(fetched_metadata.raw, metadata_json);
assert_eq!(
Expand All @@ -484,10 +505,17 @@ mod tests {

#[test]
fn handle_invalid_onchain_metadata_format() {
let contract_address = "0x1234567890123456789012345678901234567890";
let invalid_uri = "data:application/json;utf8,invalid_json";

let result = fetch_onchain_metadata(invalid_uri);

let result = fetch_onchain_metadata(contract_address, invalid_uri);
assert!(result.is_err() || result.unwrap().normalized.name.is_none());
}

#[test]
fn test_normalize_onchain_data() {
let contract_address = "0x0158160018d590d93528995b340260e65aedd76d28a686e9daa5c4e8fad0c5dd";
let uri = r#"data:application/json;utf8,{"name":""Pandemonium%20Growl"%20Wyvern","description":"Beasts","attributes":[{"trait_type":"prefix","value":"Pandemonium"},{"trait_type":"name","value":"Wyvern"},{"trait_type":"suffix","value":"Growl"},{"trait_type":"type","value":"Hunter"},{"trait_type":"tier","value":3},{"trait_type":"level","value":115},{"trait_type":"health","value":511}],"image":"data:image/svg+xml;utf8,<svg%20width=\"100%\"%20height=\"100%\"%20viewBox=\"0%200%2020000%2020000\"%20xmlns=\"http://www.w3.org/2000/svg\"><style>svg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAAxQTFRFAAAAAAAAAP8AAAD/0o8flwAAAAR0Uk5TAP///7MtQIgAAAC8SURBVDiNnZILDsMgDEMT+f533gSx49DPpLVSK/DDJIbIH088KoADqKHJNRVz2HLWVFD/fn11rhcb4GqYWRELWP/Fb19utokw/VILAdKlGgSwiyrMHnpE69rCCG4BJNpKLXoXULhqsqNWcZ45PWOzUEwj7ulgy+w4Vw0+raXDYepW7c2FUb14AOgHazO9CBztRt4Rdl4CjlPWsIHz2h7AtMAd0NPwQLtNBTp0z4EXZuhnUJjndQVeuvgb+AA3cQYBxBXi6QAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-size:contain;background-position:center;image-rendering:-webkit-optimize-contrast;-ms-interpolation-mode:nearest-neighbor;image-rendering:-moz-crisp-edges;image-rendering:pixelated;}</style></svg>"}"#;
let normalized = normalize_onchain_data(contract_address, uri);
assert_eq!(normalized, "data:application/json;utf8,{\"name\":\"Pandemonium Growl Wyvern\",\"description\":\"Beasts\",\"attributes\":[{\"trait_type\":\"prefix\",\"value\":\"Pandemonium\"},{\"trait_type\":\"name\",\"value\":\"Wyvern\"},{\"trait_type\":\"suffix\",\"value\":\"Growl\"},{\"trait_type\":\"type\",\"value\":\"Hunter\"},{\"trait_type\":\"tier\",\"value\":3},{\"trait_type\":\"level\",\"value\":115},{\"trait_type\":\"health\",\"value\":511}],\"image\":\"data:image/svg+xml;utf8,<svg width=\\\"100%\\\" height=\\\"100%\\\" viewBox=\\\"0 0 20000 20000\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><style>svg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAAxQTFRFAAAAAAAAAP8AAAD/0o8flwAAAAR0Uk5TAP///7MtQIgAAAC8SURBVDiNnZILDsMgDEMT+f533gSx49DPpLVSK/DDJIbIH088KoADqKHJNRVz2HLWVFD/fn11rhcb4GqYWRELWP/Fb19utokw/VILAdKlGgSwiyrMHnpE69rCCG4BJNpKLXoXULhqsqNWcZ45PWOzUEwj7ulgy+w4Vw0+raXDYepW7c2FUb14AOgHazO9CBztRt4Rdl4CjlPWsIHz2h7AtMAd0NPwQLtNBTp0z4EXZuhnUJjndQVeuvgb+AA3cQYBxBXi6QAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-size:contain;background-position:center;image-rendering:-webkit-optimize-contrast;-ms-interpolation-mode:nearest-neighbor;image-rendering:-moz-crisp-edges;image-rendering:pixelated;}</style></svg>\"}");
}
}
13 changes: 10 additions & 3 deletions packages/core/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@
import type { Config } from "jest";

const config: Config = {
moduleNameMapper: {
"^(\\.{1,2}/.*)\\.js$": "$1"
},
// moduleNameMapper: {
// "^(\\.{1,2}/.*)\\.js$": "$1"
// },
preset: "ts-jest/presets/default-esm",
testMatch: ["**/?(*.)test.ts?(x)"],
globals: {
"ts-jest": {
tsConfig: "tsconfig.json",
diagnostics: false
}
},
setupFiles: ["<rootDir>/tests/setup.ts"]
};

Expand Down
4 changes: 2 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
},
"dependencies": {
"@scure/starknet": "^0.3.0",
"starknet": "^6.9.0"
"starknet": "^6.11.0"
},
"devDependencies": {
"@ark-project/eslint-config": "workspace:*",
Expand Down Expand Up @@ -55,4 +55,4 @@
"optional": true
}
}
}
}
37 changes: 37 additions & 0 deletions packages/core/src/actions/fees/getArkFees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Contract } from "starknet";

import { Config } from "../../createConfig.js";

export const ABI = [
{
type: "struct",
name: "ark_oz::erc2981::fees::FeesRatio",
members: [
{ name: "numerator", type: "core::integer::u256" },
{ name: "denominator", type: "core::integer::u256" }
]
},
{
type: "function",
name: "get_ark_fees",
inputs: [],
outputs: [{ type: "ark_oz::erc2981::fees::FeesRatio" }],
state_mutability: "view"
}
] as const;

export async function getArkFees(config: Config) {
const contract = new Contract(
ABI,
config.starknetExecutorContract,
config.starknetProvider
).typedv2(ABI);

const { numerator, denominator } = await contract.get_ark_fees();

return {
formatted: ((Number(numerator) / Number(denominator)) * 100).toFixed(2),
numerator,
denominator
};
}
46 changes: 46 additions & 0 deletions packages/core/src/actions/fees/getBrokerFees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { CallData, Contract } from "starknet";

import { Config } from "../../createConfig.js";

export const ABI = [
{
type: "struct",
name: "ark_oz::erc2981::fees::FeesRatio",
members: [
{ name: "numerator", type: "core::integer::u256" },
{ name: "denominator", type: "core::integer::u256" }
]
},
{
type: "function",
name: "get_broker_fees",
inputs: [
{
name: "broker_address",
type: "core::starknet::contract_address::ContractAddress"
}
],
outputs: [{ type: "ark_oz::erc2981::fees::FeesRatio" }],
state_mutability: "view"
}
] as const;

export async function getBrokerFees(config: Config, brokerAddress: string) {
const contract = new Contract(
ABI,
config.starknetExecutorContract,
config.starknetProvider
).typedv2(ABI);

const { numerator, denominator } = await contract.get_broker_fees(
CallData.compile({
broker_address: brokerAddress
})
);

return {
formatted: ((Number(numerator) / Number(denominator)) * 100).toFixed(2),
numerator,
denominator
};
}
62 changes: 62 additions & 0 deletions packages/core/src/actions/fees/getCollectionCreatorFees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { CallData, Contract } from "starknet";
import { toHex } from "viem";

import { Config } from "../../createConfig.js";

export const ABI = [
{
type: "struct",
name: "ark_oz::erc2981::fees::FeesRatio",
members: [
{ name: "numerator", type: "core::integer::u256" },
{ name: "denominator", type: "core::integer::u256" }
]
},
{
type: "function",
name: "get_collection_creator_fees",
inputs: [
{
name: "nft_address",
type: "core::starknet::contract_address::ContractAddress"
}
],
outputs: [
{
type: "(core::starknet::contract_address::ContractAddress, ark_oz::erc2981::fees::FeesRatio)"
}
],
state_mutability: "view"
}
] as const;

export async function getCollectionCreatorFees(
config: Config,
tokenAddress: string
) {
const contract = new Contract(
ABI,
config.starknetExecutorContract,
config.starknetProvider
).typedv2(ABI);

const raw = await contract.get_collection_creator_fees(
CallData.compile({
nft_address: tokenAddress
})
);
const creator = toHex(raw[0] as bigint);
const { numerator, denominator } = raw[1] as {
numerator: bigint;
denominator: bigint;
};

return {
creator,
fees: {
formatted: ((Number(numerator) / Number(denominator)) * 100).toFixed(2),
numerator,
denominator
}
};
}
50 changes: 50 additions & 0 deletions packages/core/src/actions/fees/getDefaultCreatorFees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Contract } from "starknet";
import { toHex } from "viem";

import { Config } from "../../createConfig.js";

export const ABI = [
{
type: "struct",
name: "ark_oz::erc2981::fees::FeesRatio",
members: [
{ name: "numerator", type: "core::integer::u256" },
{ name: "denominator", type: "core::integer::u256" }
]
},
{
type: "function",
name: "get_default_creator_fees",
inputs: [],
outputs: [
{
type: "(core::starknet::contract_address::ContractAddress, ark_oz::erc2981::fees::FeesRatio)"
}
],
state_mutability: "view"
}
] as const;

export async function getDefaultCreatorFees(config: Config) {
const contract = new Contract(
ABI,
config.starknetExecutorContract,
config.starknetProvider
).typedv2(ABI);

const raw = await contract.get_default_creator_fees();
const creator = toHex(raw[0] as bigint);
const { numerator, denominator } = raw[1] as {
numerator: bigint;
denominator: bigint;
};

return {
creator,
fees: {
formatted: ((Number(numerator) / Number(denominator)) * 100).toFixed(2),
numerator,
denominator
}
};
}
Loading

0 comments on commit 9219328

Please sign in to comment.