diff --git a/.changeset/brown-walls-roll.md b/.changeset/brown-walls-roll.md new file mode 100644 index 000000000..c7652517d --- /dev/null +++ b/.changeset/brown-walls-roll.md @@ -0,0 +1,7 @@ +--- +'@openzeppelin/wizard-stellar': patch +'@openzeppelin/wizard-common': patch +'@openzeppelin/contracts-mcp': patch +--- + +Add tokenUri setting for stellar non fungible model diff --git a/packages/common/src/ai/descriptions/stellar.ts b/packages/common/src/ai/descriptions/stellar.ts index 18532c024..752475567 100644 --- a/packages/common/src/ai/descriptions/stellar.ts +++ b/packages/common/src/ai/descriptions/stellar.ts @@ -22,6 +22,7 @@ export const stellarNonFungibleDescriptions = { enumerable: 'Whether the NFTs are enumerable (can be iterated over).', consecutive: 'To batch mint NFTs instead of minting them individually (sequential minting is mandatory).', sequential: 'Whether the IDs of the minted NFTs will be sequential.', + tokenUri: 'The metadata URI returned by the token contract for every NFT.', }; export const stellarStablecoinDescriptions = { diff --git a/packages/core/stellar/src/generate/non-fungible.ts b/packages/core/stellar/src/generate/non-fungible.ts index 4c008ad35..b86fb5c86 100644 --- a/packages/core/stellar/src/generate/non-fungible.ts +++ b/packages/core/stellar/src/generate/non-fungible.ts @@ -8,6 +8,7 @@ const booleans = [true, false]; const blueprint = { name: ['MyToken'], symbol: ['MTK'], + tokenUri: ['https://www.mytoken.com'], burnable: booleans, pausable: booleans, upgradeable: booleans, diff --git a/packages/core/stellar/src/non-fungible.test.ts b/packages/core/stellar/src/non-fungible.test.ts index 5fddbee3b..3293cac7a 100644 --- a/packages/core/stellar/src/non-fungible.test.ts +++ b/packages/core/stellar/src/non-fungible.test.ts @@ -153,6 +153,10 @@ testNonFungible('non-fungible - complex name', { pausable: true, }); +testNonFungible('non-fungible custom token uri', { + tokenUri: 'https://example.com/nfts/', +}); + testAPIEquivalence('non-fungible API default'); testAPIEquivalence('non-fungible API basic', { name: 'CustomToken', symbol: 'CTK' }); diff --git a/packages/core/stellar/src/non-fungible.test.ts.md b/packages/core/stellar/src/non-fungible.test.ts.md index 62e5ccafd..60e88769c 100644 --- a/packages/core/stellar/src/non-fungible.test.ts.md +++ b/packages/core/stellar/src/non-fungible.test.ts.md @@ -22,7 +22,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -55,7 +55,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -98,7 +98,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env, owner: Address) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -168,7 +168,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env, owner: Address) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -254,7 +254,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env, owner: Address) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -303,7 +303,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -347,7 +347,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env, owner: Address) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -405,7 +405,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env, owner: Address) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -467,7 +467,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env, owner: Address) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -553,7 +553,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env, owner: Address) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -647,7 +647,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -683,7 +683,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env, owner: Address) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -739,7 +739,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl MyToken {␊ pub fn __constructor(e: &Env, owner: Address) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "MyToken");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -842,7 +842,7 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl CustomToken {␊ pub fn __constructor(e: &Env, owner: Address) {␊ - let uri = String::from_str(e, "www.mytoken.com");␊ + let uri = String::from_str(e, "https://www.mytoken.com");␊ let name = String::from_str(e, "Custom $ Token");␊ let symbol = String::from_str(e, "MTK");␊ Base::set_metadata(e, uri, name, symbol);␊ @@ -908,3 +908,36 @@ Generated by [AVA](https://avajs.dev). #[contractimpl]␊ impl Ownable for CustomToken {}␊ ` + +## non-fungible custom token uri + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Stellar Soroban Contracts ^0.4.1␊ + #![no_std]␊ + ␊ + use soroban_sdk::{contract, contractimpl, Env, String};␊ + use stellar_macros::default_impl;␊ + use stellar_tokens::non_fungible::{Base, NonFungibleToken};␊ + ␊ + #[contract]␊ + pub struct MyToken;␊ + ␊ + #[contractimpl]␊ + impl MyToken {␊ + pub fn __constructor(e: &Env) {␊ + let uri = String::from_str(e, "https://example.com/nfts/");␊ + let name = String::from_str(e, "MyToken");␊ + let symbol = String::from_str(e, "MTK");␊ + Base::set_metadata(e, uri, name, symbol);␊ + }␊ + }␊ + ␊ + #[default_impl]␊ + #[contractimpl]␊ + impl NonFungibleToken for MyToken {␊ + type ContractType = Base;␊ + ␊ + }␊ + ` diff --git a/packages/core/stellar/src/non-fungible.test.ts.snap b/packages/core/stellar/src/non-fungible.test.ts.snap index 92c1b0daf..52c1cee76 100644 Binary files a/packages/core/stellar/src/non-fungible.test.ts.snap and b/packages/core/stellar/src/non-fungible.test.ts.snap differ diff --git a/packages/core/stellar/src/non-fungible.ts b/packages/core/stellar/src/non-fungible.ts index 7230a3c80..1859626f6 100644 --- a/packages/core/stellar/src/non-fungible.ts +++ b/packages/core/stellar/src/non-fungible.ts @@ -16,6 +16,7 @@ import { toByteArray } from './utils/convert-strings'; export const defaults: Required = { name: 'MyToken', symbol: 'MTK', + tokenUri: 'https://www.mytoken.com', burnable: false, enumerable: false, consecutive: false, @@ -34,6 +35,7 @@ export function printNonFungible(opts: NonFungibleOptions = defaults): string { export interface NonFungibleOptions extends CommonContractOptions { name: string; symbol: string; + tokenUri?: string; burnable?: boolean; enumerable?: boolean; consecutive?: boolean; @@ -47,6 +49,7 @@ function withDefaults(opts: NonFungibleOptions): Required { return { ...opts, ...withCommonContractDefaults(opts), + tokenUri: opts.tokenUri ?? defaults.tokenUri, burnable: opts.burnable ?? defaults.burnable, consecutive: opts.consecutive ?? defaults.consecutive, enumerable: opts.enumerable ?? defaults.enumerable, @@ -87,7 +90,7 @@ export function buildNonFungible(opts: NonFungibleOptions): Contract { throw new OptionsError(errors); } - addBase(c, toByteArray(allOpts.name), toByteArray(allOpts.symbol), allOpts.pausable); + addBase(c, toByteArray(allOpts.name), toByteArray(allOpts.symbol), toByteArray(allOpts.tokenUri), allOpts.pausable); if (allOpts.pausable) { addPausable(c, allOpts.access); @@ -119,9 +122,9 @@ export function buildNonFungible(opts: NonFungibleOptions): Contract { return c; } -function addBase(c: ContractBuilder, name: string, symbol: string, pausable: boolean) { +function addBase(c: ContractBuilder, name: string, symbol: string, tokenUri: string, pausable: boolean) { // Set metadata - c.addConstructorCode('let uri = String::from_str(e, "www.mytoken.com");'); + c.addConstructorCode(`let uri = String::from_str(e, "${tokenUri}");`); c.addConstructorCode(`let name = String::from_str(e, "${name}");`); c.addConstructorCode(`let symbol = String::from_str(e, "${symbol}");`); c.addConstructorCode(`Base::set_metadata(e, uri, name, symbol);`); diff --git a/packages/mcp/src/stellar/schemas.ts b/packages/mcp/src/stellar/schemas.ts index 0d80599e6..5d667b03e 100644 --- a/packages/mcp/src/stellar/schemas.ts +++ b/packages/mcp/src/stellar/schemas.ts @@ -57,6 +57,7 @@ export const stablecoinSchema = { export const nonFungibleSchema = { name: z.string().describe(commonDescriptions.name), symbol: z.string().describe(commonDescriptions.symbol), + tokenUri: z.string().optional().describe(stellarNonFungibleDescriptions.tokenUri), burnable: z.boolean().optional().describe(commonDescriptions.burnable), enumerable: z.boolean().optional().describe(stellarNonFungibleDescriptions.enumerable), consecutive: z.boolean().optional().describe(stellarNonFungibleDescriptions.consecutive), diff --git a/packages/mcp/src/stellar/tools/non-fungible.test.ts b/packages/mcp/src/stellar/tools/non-fungible.test.ts index 22464b5e9..59643e92d 100644 --- a/packages/mcp/src/stellar/tools/non-fungible.test.ts +++ b/packages/mcp/src/stellar/tools/non-fungible.test.ts @@ -34,6 +34,7 @@ test('basic', async t => { const params: z.infer = { name: 'TestToken', symbol: 'TST', + tokenUri: 'https://example.com/nft/', }; await assertAPIEquivalence(t, params, nonFungible.print); }); @@ -42,6 +43,7 @@ test('all', async t => { const params: DeepRequired> = { name: 'TestToken', symbol: 'TST', + tokenUri: 'https://example.com/nft/', burnable: true, enumerable: true, consecutive: true, diff --git a/packages/mcp/src/stellar/tools/non-fungible.ts b/packages/mcp/src/stellar/tools/non-fungible.ts index c46efc2a5..9c120e39c 100644 --- a/packages/mcp/src/stellar/tools/non-fungible.ts +++ b/packages/mcp/src/stellar/tools/non-fungible.ts @@ -10,10 +10,23 @@ export function registerStellarNonFungible(server: McpServer): RegisteredTool { 'stellar-non-fungible', makeDetailedPrompt(stellarPrompts.NonFungible), nonFungibleSchema, - async ({ name, symbol, burnable, enumerable, consecutive, pausable, mintable, sequential, upgradeable, info }) => { + async ({ + name, + symbol, + tokenUri, + burnable, + enumerable, + consecutive, + pausable, + mintable, + sequential, + upgradeable, + info, + }) => { const opts: NonFungibleOptions = { name, symbol, + tokenUri, burnable, enumerable, consecutive, diff --git a/packages/ui/api/ai-assistant/function-definitions/stellar.ts b/packages/ui/api/ai-assistant/function-definitions/stellar.ts index 61b415283..bab3a484d 100644 --- a/packages/ui/api/ai-assistant/function-definitions/stellar.ts +++ b/packages/ui/api/ai-assistant/function-definitions/stellar.ts @@ -99,6 +99,10 @@ export const stellarNonFungibleAIFunctionDefinition = { type: 'boolean', description: stellarNonFungibleDescriptions.sequential, }, + tokenUri: { + type: 'string', + description: stellarNonFungibleDescriptions.tokenUri, + }, upgradeable: { type: 'boolean', description: stellarCommonDescriptions.upgradeable, diff --git a/packages/ui/src/stellar/NonFungibleControls.svelte b/packages/ui/src/stellar/NonFungibleControls.svelte index 322e6845e..5cbc71e03 100644 --- a/packages/ui/src/stellar/NonFungibleControls.svelte +++ b/packages/ui/src/stellar/NonFungibleControls.svelte @@ -50,6 +50,14 @@ + +