创建灵活的NFT(第2部分)
第2部分
第1步:制作新的netlify项目
我们通过在与其余代码相同的文件夹中创建一个网页来开始第2部分。
touch ./index.html && echo "hello world : )" > ./index.html
太棒了,这是一个美丽的网站我们将它部署到互联网上。创建一个git repo,提交你的代码,并推送到原点:
git add . && git commit -m 'new website' && git push -u origin master
我使用netlify进行托管,因为它们有一个一体化的软件包,用于从存储库部署站点,运行构建过程,为自定义域添加SSL以及添加lambda函数的能力。他们还有身份验证和表单处理,但我从未使用过这些功能。您可以使用AWS或Google firebase。访问netlify.com并使用您的github / gitlab / bitbucket帐户注册。
我们正在创建一个API端点,它返回NFT的元数据。我知道你在想什么,“这不是一个邪恶的中心化解决方案吗?”。是的。为什么?因为替代方案仍然很糟糕。直到我们生活在一个我可以预期我的IPFS文件在我停止播种之后仍然存在的世界,以及我不必永远等待内容的地方,我们必须使用当前的互联网基础设施。如果你看看任何成功的NFT项目,他们都在做同样的事情。最大的NFT市场opensea.io缓存了他们可以找到并直接提供的所有NFT数据。这是因为它比依靠去中心化的解决方案更好。当去中心化解决方案可行时,我们的NFT将具有可升级的元数据端点
回到netlify,我们允许他们对我们的仓库进行API访问,以便他们可以部署更改。
找到我们的回购并选择它。
我们不需要添加构建命令或发布目录,因为我们的网站只是一个index.html文件,它位于项目根目录中。您可能已经在master
分支上,因此无需更改(尽管netlify可以根据需要自动部署新域上的每个分支)。然后单击“部署站点”。
如果要从自动生成的名称更改站点名称,请单击“ 站点设置”,然后向下滚动到“ 更改站点名称”。我将我改为“block-workshop”,一旦部署过程完成,就可以在https://block-workshop.netlify.com上找到它。
如果一切顺利,你应该看到这个美丽的网站:
第2步:安装netlify lambda
安装netlify-lambda
为dev依赖项,以便我们可以访问它npx
。这是一个用于构建lambda函数并在本地提供它的实用程序,因此您可以在部署它们之前测试函数。
yarn add netlify-lambda -D
# or
npm install netlify-lambda --save-dev
添加lambda函数所在的目录。称之为lambda,因为这是有道理的。
mkdir lambda
为netlify 创建配置.toml文件,以定义我们的函数的服务位置:
touch netlify.toml
现在将密钥添加functions
到toml文件中,该文件是在netlify-lambda
构建它们之后提供函数的位置:
[build]
functions = "functions"
在lambda文件夹中创建一个虚函数:
touch ./lambda/helloworld.js
添加netlify从其文档中提供的样板:
exports.handler = function(event, context, callback) {
callback(null, {
statusCode: 200,
body: "Hello, World"
});
};
该文件导出一个名为的函数handler
。这与AWS用于其lambda函数的格式相同(因为netlify是AWS的盘点器)。如果您使用了AWS的lambda函数,则可以将其与netlify一起使用,如果您有关于这些函数的高级问题请求,请在查询中添加“AWS”而不是“netlify”。
运行本地服务器,以便我们可以使用该netlify-lambda
实用程序测试端点:
$npx netlify-lambda serve lambda
netlify-lambda: Starting server
Lambda server is listening on 9000
Hash: 47a70dc1b032c7c81a89
Version: webpack 4.27.1
Time: 745ms
Built at: 2018-12-13 18:52:53
Asset Size Chunks Chunk Names
helloworld.js 1.03 KiB 0 [emitted] helloworld
Entrypoint helloworld = helloworld.js
[0] ./helloworld.js 129 bytes {0} [built]
这将构建一个新的函数文件夹,其中编译和提供helloworld.js文件。它可以从端口访问9000
,默认情况下可以访问http://localhost:9000/helloworld
提交您的代码并推送到您的仓库。Netlify应该注意到推送master
和自动部署它。
git add . && git commit -m 'Step 2: Install netlify lambda' && git push
您现在可以访问netlify上的函数部分,其中您有一个helloworld
函数
部署完成后,您应该可以通过https:// {SITE_NAME} .netlify.com / .netlify / functions / helloworld访问它
这是函数的已部署格式,因此您的当前路由没有任何名称冲突。这是不方便的语法,我们将在稍后的步骤中将代理规则添加到元数据端点。
第3步:添加元数据
现在我们已经创建了一个虚拟端点,让我们创建一个更有用的端点。在lambda目录中创建一个名为metadata.js的新文件,并使用之前的hello world代码填充它。(或复制helloworld.js文件):
cp ./lambda/helloworld.js ./lambda/metadata.js
现在花点时间阅读helloworld.js文件:
exports.handler = function(event, context, callback) {
callback(null, {
statusCode: 200,
body: "Hello, World"
});
};
处理函数有3个参数:
event
这是触发该功能的事件context
这是事件的背景callback
它结束请求并用内容和标题信息填充它。
我们处理对令牌元数据的请求,这些请求遵循我们在Metadata.sol合约中构建的格式。这意味着它是一个GET
请求,其中包含内置于URL路由中的令牌ID,例如https://domain.com/metadata/{tokenId}
。要传递GET
参数,我们使用类似的格式https://domain.com/metadata?tokenId={tokenId}
。我们可以定义我们tokenURI
遵循这样的格式,但这很难看。
让我们现在使用这种格式,稍后进行改进。我们记录事件以查看是否可以找到tokenId
传递给URL 的参数。这在我们的本地设置中更容易,因此请遵循URL模式http://localhost:9000/metadata?tokenId=666
将一些console.log
s 添加到metadata.js处理函数中,以便我们可以读取这些参数中发生的情况:
exports.handler = function(event, context, callback) {
console.log("EVENT", event)
console.log("CONTEXT", context)
callback(null, {
statusCode: 200,
body: "Hello, World"
});
};
重新启动该netlify-lambda
实用程序(如果它仍在运行)并访问该URL:
npx netlify-lambda serve lambda
如果您检查控制台运行的服务器,你看到的内容event
,并context
和tokenId
下queryStringParameters
:
$npx netlify-lambda serve lambda
netlify-lambda: Starting server
Lambda server is listening on 9000
Hash: 6507b49ec95292f0e68a
Version: webpack 4.27.1
Time: 665ms
Built at: 2018-12-13 19:18:56
Asset Size Chunks Chunk Names
helloworld.js 1.03 KiB 0 [emitted] helloworld
metadata.js 1.08 KiB 1 [emitted] metadata
Entrypoint helloworld = helloworld.js
Entrypoint metadata = metadata.js
[0] ./helloworld.js 129 bytes {0} [built]
[1] ./metadata.js 195 bytes {1} [built]
Request from ::1: GET /metadata?tokenId=666
EVENT { path: '/metadata',
httpMethod: 'GET',
queryStringParameters: { tokenId: '666' },
headers:
{ host: 'localhost:9000',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9' },
body: 'W29iamVjdCBPYmplY3Rd',
isBase64Encoded: true }
CONTEXT {}
Response with status 200 in 8 ms.
为了符合EIP-721和EIP-1047,令牌元数据JSON模式应遵循以下格式:
{
"title": "Asset Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this token represents",
},
"description": {
"type": "string",
"description": "Describes the asset to which this token represents",
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image/* representing the asset to which this token represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive.",
}
}
}
让我们尝试返回这个,但是用名称替换名称tokenId
,然后返回一个自动生成的图像https://dummyimage.com/600x400/000000/fff/&text=test%20image
。
const tokenId = event.queryStringParameters.tokenId
const metadata = {
"name": "Token #" + tokenId,
"description": "Describes the asset to which this token represents",
"image": "https://dummyimage.com/600x400/000000/fff/&text=token%20" + tokenId,
}
在callback
函数中返回它,并在返回之前对JSON对象进行字符串化:
callback(null, {
statusCode: 200,
body: JSON.stringify(metadata)
});
当我们检查我们的端点时(如果你有一个更漂亮的浏览器扩展名)它会返回:
第4步:添加代理路由
在netlify上,我们仍然使用不方便的URL格式/.netlify/functions/metadata?tokenId=666
来查看新端点。打开netlify.toml文件并添加一些重写规则,以便我们可以将一个漂亮的URL /metadata/666
转换为我们的lambda函数可以理解的内容/.netlify/functions/metadata?tokenId=666
:
[build]
functions = "functions"
[[redirects]]
from = "/metadata/:tokenId"
to = "/.netlify/functions/metadata?tokenId=:tokenId"
status = 200
这会将查询重定向/metadata
到该位置的任何内容/.netlify/functions/metadata
。该:tokenId
占位符指定的值应结转到同一位置的其他网址。它应该在标题中返回的状态200
意味着成功。
第5步:添加opensea.io
为了确保我们的元数据显示在像opensea这样的网站上,我们希望提供服务理解的格式。该Opensea文档说,他们想到的是附着在下面的示例中的元数据:
{
"description": "Friendly OpenSea Creature that enjoys long swims in the ocean.",
"external_url": "https://openseacreatures.io/3",
"image": "https://storage.googleapis.com/opensea-prod.appspot.com/puffs/3.png",
"name": "Dave Starbelly",
"attributes": [ ... ],
}
使用附加attributes
键可以填充如下:
{
"attributes": [
{
"trait_type": "base",
"value": "starfish"
},
{
"trait_type": "eyes",
"value": "big"
},
{
"trait_type": "mouth",
"value": "surprised"
},
{
"trait_type": "level",
"value": 5
},
{
"trait_type": "stamina",
"value": 1.4
},
{
"trait_type": "personality",
"value": "sad"
},
{
"display_type": "boost_number",
"trait_type": "aqua_power",
"value": 40
},
{
"display_type": "boost_percentage",
"trait_type": "stamina_increase",
"value": 10
},
{
"display_type": "number",
"trait_type": "generation",
"value": 2
}
]
}
添加一些属性到我们的端点。也许我们tokenId
可以反映一个星座:
exports.handler = function(event, context, callback) {
const tokenId = event.queryStringParameters.tokenId
const metadata = {
"name": "Token #" + tokenId,
"external_url": "https://block-workshop.netlify.com/",
"description": "This is a very basic NFT with token Id #" + tokenId,
"image": "https://dummyimage.com/600x400/000000/fff/&text=token%20" + tokenId,
"attributes": [
{
"trait_type": "zodiac",
"value": returnZodiac(tokenId)
}
]
}
callback(null, {
statusCode: 200,
body: JSON.stringify(metadata)
});
};
function returnZodiac(tokenId) {
const month = ((tokenId - 1) % 12) + 1
switch(month) {
case(1):
return 'Capricorn'
case(2):
return 'Aquarius'
case(3):
return 'Pisces'
case(4):
return 'Aries'
case(5):
return 'Taurus'
case(6):
return 'Gemini'
case(7):
return 'Cancer'
case(8):
return 'Leo'
case(9):
return 'Virgo'
case(10):
return 'Libra'
case(11):
return 'Scorpio'
case(12):
return 'Sagittarius'
}
}
第6步:添加稀有物
另一个受欢迎的NFT市场是稀有市场。我们也要坚持他们的格式:
{
"name": "Robot token #14",
"image_url": "https://www.robotgame.com/images/14.png",
"home_url": "https://www.robotgame.com/robots/14.html",
"description": "This is the amazing Robot #14, please buy me!",
"properties": [
{"key": "generation", "value": 4, type: "integer"},
{"key": "cooldown", "value": "slow", type: "string"}
],
"tags": ["red","rare","fire"]
}
你知道什么它遵循自己的规范您现在可以了解为什么保持元数据端点的灵活性非常重要。直到我们生活在一个已经确定了每个人都使用的标准并且不在netlify上的lambda函数上托管的世界。
添加信息到我们的令牌,所以它也遵守稀有:
exports.handler = function(event, context, callback) {
const tokenId = event.queryStringParameters.tokenId
const metadata = {
// both opensea and rarebits
"name": "Token #" + tokenId,
"description": "This is a basic NFT with token Id #" + tokenId,
// opensea
"external_url": "https://block-workshop.netlify.com/",
// rarebits
"home_url": "https://block-workshop.netlify.com/",
// opensea
"image": "https://dummyimage.com/600x400/000/fff/&text=token%20" + tokenId,
// rarebits
"image_url": "https://dummyimage.com/600x400/000/fff/&text=token%20" + tokenId,
// opensea
"attributes": [
{
"trait_type": "zodiac",
"value": returnZodiac(tokenId)
}
],
// rarebits
"properties": [
{"key": "zodiac", "value": returnZodiac(tokenId), type: "string"},
],
// rarebits
"tags": ["cool","hot","mild"]
}
callback(null, {
statusCode: 200,
body: JSON.stringify(metadata)
});
};
现在我们返回了一个胖的json对象。
第7步:重新部署并铸造令牌
现在我们有一个元数据API端点,我们不需要做任何事情来为它提供服务。我们甚至有一个缩小的网站,并在内容交付网络中播种。我们所缺少的只是我们的令牌。
当我们部署我们的令牌时,我们使用了返回的元数据端点https://domain.com/metadata/{tokenId}
,但domain.com
不是我们的域我们必须更新我们的元数据端点。
值得庆幸的是,我们建立了这种能力和迁移。在Metadata.sol合约内部使用我们的netlify子域更新URI:
function tokenURI(uint _tokenId) public pure returns (string memory _infoUrl) {
string memory base = "https://block-workshop.netlify.com/metadata/";
string memory id = uint2str(_tokenId);
return base.toSlice().concat(id.toSlice());
}
运行迁移,以便仅替换元数据并在合约内部更新:
$truffle migrate --network rinkeby -f 3 --to 3
...
Using network 'rinkeby'.
Running migration: 3_update_metadata.js
Running step...
Replacing Metadata...
... 0xe596fcf7f20073988c4c57167d19a529b086ddd978ce386bf66485a97f3ad2d9
Metadata: 0xfb66019e647cec020cf5d1277c81ad463e4574a4
Metadata deployed at: 0xfb66019e647cec020cf5d1277c81ad463e4574a4
代币 deployed at: 0x1170a2c7d4913d399f74ee5270ac65730ff961bf
... 0xc3316fa072e84038ee30c360bc70cdc4107d3fcb74780e33e34b0e117e1534aa
Saving successful migration to network...
... 0x416630f6fad98eef2f065014c55ac8b43901ef804435b92d4d02f804a7d4c242
Saving artifacts...
返回我们的etherscan认证令牌并铸造我们的第一个令牌。您应该看到我们的updateMetadata
交易现在列在那里。
由于我使用的是与我的部署帐户相同的Metamask帐户,因此我有权限制一个令牌。打开写入合约选项卡,使用Metamask进行身份验证,并填写令牌。
由于我添加了自己的地址作为收件人,我应该成为令牌#1的自豪拥有者。我可以使用之前看到的etherscan的标记视图进行检查。
哇,有一个令牌
打开opensea,看看他们是否注意到我们存在。对于rarebit和opensea,你必须要求他们在侧边栏显示之前跟踪你的令牌,但是你可以通过将合约地址硬编码到URL来跳过它。了解我们的令牌地址是在0x1170a2c7d4913d399f74ee5270ac65730ff961bf
和我们的tokenId
就是1
我们能够访问这样的URL的rinkeby版本:
https://rinkeby.opensea.io/assets/0x1170a2c7d4913d399f74ee5270ac65730ff961bf/1
哇,他们甚至知道我们的代币的星座
正式添加到应用程序,我们可以在rinkeby部分看到它。
我们起来了
我们也将它添加到稀有。
下一步
- 制作一个更有趣的生成形象
- 一个例子是ENSNifty.com使用的cloudinary的色调旋转(github链接)
- 将数据库添加到lambda函数以获取更丰富的元数据
- 奖励:在推特上开始讨论真正的去中心化的意义
原文:https://kauri.io/article/85f570393e1448a7a94e2eab1bae6e48/v2/creating-a-flexible-nft-(part-2)