feat: gasless mint

This commit is contained in:
Zitao Xiong
2023-12-13 03:11:02 +08:00
parent 944a401b8c
commit a9b3f541e1
9 changed files with 179 additions and 33 deletions

View File

@@ -79,7 +79,7 @@ module storytime::story_nft {
}
// ===== Entrypoints =====
#[lint_allow(self_transfer)]
public fun mint(name: String, image_url: String, ctx: &mut TxContext) {
let sender = tx_context::sender(ctx);
let id = object::new(ctx);
@@ -101,6 +101,26 @@ module storytime::story_nft {
transfer::public_transfer(nft, sender);
}
public fun mint_to(to: address, name: String, image_url: String, ctx: &mut TxContext) {
let id = object::new(ctx);
let chapters = vector[];
let nft = StoryNFT {
id,
name,
image_url,
chapters,
auther: to
};
event::emit(NFTMinted {
object_id: object::id(&nft),
creator: to,
name: nft.name,
});
transfer::public_transfer(nft, to);
}
/// Transfer `nft` to `recipient`
public fun transfer(
nft: StoryNFT, recipient: address, _: &mut TxContext

View File

@@ -28,24 +28,6 @@ async function main() {
],
});
// const [coin] = txb.splitCoins(txb.gas, [100]);
// transfer the split coin to a specific address
// txb.transferObjects([coin], '0x380254779600ed29cb70c917255b084d12b5a760c4dadeceb8f7673d0fc99d1d');
// const bytes = await txb.build();
// const serializedSignature = (await keypair.signTransactionBlock(bytes))
// .signature;
// // verify the signature locally
// const verified = await keypair
// .getPublicKey()
// .verifyTransactionBlock(bytes, serializedSignature)
// if (!verified) {
// throw new Error('Signature verification failed');
// }
// define sui client for the desired network.
const client = new SuiClient({ url: getFullnodeUrl('devnet') });
const result = await client.signAndExecuteTransactionBlock({

View File

@@ -8,3 +8,4 @@ typings/
# Node.js dependency directory
node_modules/
lib/
src/credentials/admin-key.json

View File

@@ -15,7 +15,10 @@
"main": "lib/index.js",
"dependencies": {
"@google-cloud/functions-framework": "^3.3.0",
"@mysten/bcs": "^0.9.0",
"@mysten/sui.js": "^0.48.0",
"@types/express": "^4.17.21",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"firebase-admin": "^11.11.1",
"firebase-functions": "^4.3.1",
@@ -30,4 +33,4 @@
"typescript": "^5.3.3"
},
"private": true
}
}

View File

@@ -8,9 +8,18 @@ dependencies:
'@google-cloud/functions-framework':
specifier: ^3.3.0
version: 3.3.0
'@mysten/bcs':
specifier: ^0.9.0
version: 0.9.0
'@mysten/sui.js':
specifier: ^0.48.0
version: 0.48.0
'@types/express':
specifier: ^4.17.21
version: 4.17.21
dotenv:
specifier: ^16.3.1
version: 16.3.1
express:
specifier: ^4.18.2
version: 4.18.2
@@ -801,6 +810,37 @@ packages:
lodash: 4.17.21
optional: true
/@mysten/bcs@0.9.0:
resolution: {integrity: sha512-h56essa8oSS4/J0Dby8k8stMoOSt+QZIEIeZNtgTOWh9HeV69yFg2BUg/+Rk7jzfWzvUmw9lFyKNipXcD5QOTw==}
dependencies:
bs58: 5.0.0
dev: false
/@mysten/sui.js@0.48.0:
resolution: {integrity: sha512-kGeV5F3IYThiliVbIou3YOOGAr8TdNyub4hGY2gevnphFQF8SukwQk8cyzuEEsAVSDUt0rsA9xxpncSgX+RjMA==}
engines: {node: '>=16'}
dependencies:
'@mysten/bcs': 0.9.0
'@noble/curves': 1.3.0
'@noble/hashes': 1.3.3
'@scure/bip32': 1.3.3
'@scure/bip39': 1.2.1
'@suchipi/femver': 1.0.0
superstruct: 1.0.3
tweetnacl: 1.0.3
dev: false
/@noble/curves@1.3.0:
resolution: {integrity: sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==}
dependencies:
'@noble/hashes': 1.3.3
dev: false
/@noble/hashes@1.3.3:
resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==}
engines: {node: '>= 16'}
dev: false
/@protobufjs/aspromise@1.1.2:
resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
@@ -834,6 +874,25 @@ packages:
/@protobufjs/utf8@1.1.0:
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
/@scure/base@1.1.4:
resolution: {integrity: sha512-wznebWtt+ejH8el87yuD4i9xLSbYZXf1Pe4DY0o/zq/eg5I0VQVXVbFs6XIM0pNVCJ/uE3t5wI9kh90mdLUxtw==}
dev: false
/@scure/bip32@1.3.3:
resolution: {integrity: sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==}
dependencies:
'@noble/curves': 1.3.0
'@noble/hashes': 1.3.3
'@scure/base': 1.1.4
dev: false
/@scure/bip39@1.2.1:
resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==}
dependencies:
'@noble/hashes': 1.3.3
'@scure/base': 1.1.4
dev: false
/@sinclair/typebox@0.27.8:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
dev: true
@@ -855,6 +914,10 @@ packages:
'@sinonjs/commons': 3.0.0
dev: true
/@suchipi/femver@1.0.0:
resolution: {integrity: sha512-bprE8+K5V+DPX7q2e2K57ImqNBdfGHDIWaGI5xHxZoxbKOuQZn4wzPiUxOAHnsUr3w3xHrWXwN7gnG/iIuEMIg==}
dev: false
/@szmarczak/http-timer@4.0.6:
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
engines: {node: '>=10'}
@@ -1311,6 +1374,10 @@ packages:
resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==}
dev: false
/base-x@4.0.0:
resolution: {integrity: sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==}
dev: false
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
requiresBuild: true
@@ -1376,6 +1443,12 @@ packages:
update-browserslist-db: 1.0.13(browserslist@4.22.2)
dev: true
/bs58@5.0.0:
resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==}
dependencies:
base-x: 4.0.0
dev: false
/bser@2.1.1:
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
dependencies:
@@ -1702,6 +1775,11 @@ packages:
md5: 2.3.0
dev: false
/dotenv@16.3.1:
resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
engines: {node: '>=12'}
dev: false
/duplexify@4.1.2:
resolution: {integrity: sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==}
requiresBuild: true
@@ -4011,6 +4089,11 @@ packages:
requiresBuild: true
optional: true
/superstruct@1.0.3:
resolution: {integrity: sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==}
engines: {node: '>=14.0.0'}
dev: false
/supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
@@ -4099,6 +4182,10 @@ packages:
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
/tweetnacl@1.0.3:
resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
dev: false
/type-check@0.3.2:
resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}
engines: {node: '>= 0.8.0'}

View File

@@ -1,13 +0,0 @@
{
"type": "service_account",
"project_id": "storytime-web3",
"private_key_id": "c948532af7977cea6405e645825b2bdfd2c3e3d6",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDClnYzEADTVtoI\nKEt2gnpEpEKQLVOVWao7Z2QNalKtuEtZb3GqL5tFDWi5d0CzejoqADQTq30bIV/m\narYk5JnCDG8NlJ7prcJc2t5KfrKOX/ReVdy7Jn1pCy3wv7S5qChEYNOojzDufEmb\nBFclFUUGlFkZO5lgQHKQfXUr0j7khL0Z0d6QxhT8D4tEqLjCYz+ZRlPvHdEvP9kf\naBc3e/amQNXwwyhXidtOPwYsVkIBTmHBtUYXx4gcTJrOwumHwhJYQcRpgfjtt2yO\ncARYRlADtArJY+SXY6laSD9KNZfJ3CWKZtGcbtdMCOcRdMlD5hIOS7HH02Iy7wcv\nHuBckoW/AgMBAAECggEAGLTkUXZyNw3w2sFMV5dDMVej20zjWclhO8VUZv79fBuJ\ndv5snF21ZW6e7JGPha+la/N2U/BduaX4Aw3Ajm2iIjPUSTv6HhSBuFV9N6XfmQPm\nR1tijowEDLmD00IcDx337ZACaoVdLU1a8Sj7bEoc517K81EQbTAG+9aaG9QdnS5k\nD0HyHbcNneT6lQrjv+3iMh7zGwzmSWSxD81fKhrGalAB97OZwdiwZSQKp2icySRU\nsnhk0FFsggQ2KHaX26zrp0s9VSUGMvD82xDymSU6YWwrIlaC2P1lDy72bo2EaeRS\nh9qZKld7e1vFNUuKaZhiHPNF8CgEhq9ztVvOVgQW3QKBgQDmJqhDyoWSg7Qr1rba\nOs4UOigQnyRrd8vwarxyvm97tUTbzAeyaKvfxSNlxxRnGowh2Gm92sX7togJ+5w1\nnTBjxQjvvFLaTEsUdRQUPH7UcEbG71+YJpS82Izznc4UNYP3GLlsljQ20eQ+2PM/\nrHc4zm+qZb1MVoXiduCjKvU5SwKBgQDYcUh7J0TiKTDTWyyHagDVIImz0yfY3FXf\noqov8kJemApHgXSkH4NjSQV7bDpBWytt+kZEjIlLMXCx9/gUNRQIdAzX4n5qeOZr\nohRP6Fh7atycnlQQ+ifrjPcb+3OEuccxog+6nTqbTaJMig0Pa3yJqR1psIHlOxSF\nzeFzgSYw3QKBgQDRQmzT1pxEb0OuaHexTK57bbVoB4rRvAbN/f6CReyPim2le4m0\nCb/coh5hN/WxGU44p9DdMsG48GoYyZFqnhWVYV2SvKSIn73UR++NxsN63Os9jgBZ\nExtB6ZOfHeh2L4JhdTWDKb8n6QeirRfe2S09lVWqlP7dHf51vqjZMwHsqQKBgQC/\no9YETOm9wbDcgs6ze1UPYAbstJqEddqG73T2jO7c6Iu5clL/enOP9jinZlVSRtH2\nR3HuAe0SKc1ZlnAOHE0HixFQGNfLmA6U4GZRtiyZ4i1BcyKGAahU9HRbT1GiBAft\n10tL9SjOF3gLgvQ7YfVbweQsDz+D4sKyEm97IK/a9QKBgDNz3sj+qkbgMn0sW2Fw\n3o5h9oWvEk2Zvvpo8+g/w3xEgoS2RbboqsCAN+JNw/fKecFrlZA97xVgd6fQjZNb\nfnqPRx3uHFKQwaOGaEAc5yTA5JFDSBdeRO5qXrHnPTyqNbwk4hzIAz+LcSn0WJgW\neG0OLLjO1ZadmECODGEHlp8U\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-i9jvg@storytime-web3.iam.gserviceaccount.com",
"client_id": "112791080690462614850",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-i9jvg%40storytime-web3.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

View File

@@ -0,0 +1,58 @@
import { fromHEX } from "@mysten/bcs";
import { SuiClient, getFullnodeUrl } from "@mysten/sui.js/client";
import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
import { TransactionBlock } from "@mysten/sui.js/transactions";
import dotenv from "dotenv";
import { CloudFunctionsTypeWithUid } from "./handlersType";
dotenv.config();
export const gaslessMint: CloudFunctionsTypeWithUid["gaslessMint"] = async (
to: string,
title: string,
imageUrl: string
): Promise<{ objectId: string }> => {
const key = process.env.SUI_PRIVATE_KEY;
const keypair = Ed25519Keypair.fromSecretKey(fromHEX(key!), {
skipValidation: false,
});
const pk = keypair.getPublicKey();
const sender = pk.toSuiAddress();
// create an example transaction block.
const txb = new TransactionBlock();
txb.setSender(sender);
txb.setGasBudget(5_000_000);
txb.moveCall({
target: `0x84abd49cedd1ddf02ab8e48c167df180b047cdd1e4c07d97434e382658dfffe5::story_nft::mint_to`,
arguments: [txb.pure.address(to), txb.pure(title), txb.pure(imageUrl)],
});
// define sui client for the desired network.
const client = new SuiClient({ url: getFullnodeUrl("devnet") });
const result = await client.signAndExecuteTransactionBlock({
signer: keypair,
transactionBlock: txb,
});
console.log(result);
const transactionBlock = await client.waitForTransactionBlock({
digest: result.digest,
options: {
showEvents: true,
showEffects: true,
},
});
console.log(transactionBlock);
console.log(`event: ${JSON.stringify(transactionBlock.events, null, 2)}`);
const objectId = (transactionBlock.events?.[0]?.parsedJson as any)
.object_id as string;
return {
objectId,
};
};

View File

@@ -15,6 +15,7 @@ import admin from "firebase-admin";
import adminKey from "./credentials/admin-key.json";
import { CloudFunctionsTypeWithUid } from "./handlersType";
import { getStory } from "./getStory";
import { gaslessMint } from "./gaslessMint";
export * from "./serve";
@@ -27,6 +28,7 @@ admin.initializeApp({
const handlers: CloudFunctionsTypeWithUid = {
generateImage,
getStory,
gaslessMint,
};
export const execute = onCall(

View File

@@ -3,4 +3,10 @@ export type CloudFunctionsType = {
prompt: string
): Promise<{ image_url: string; revised_prompt: string }>;
getStory(id: string): Promise<{ id: string; story: string }>;
gaslessMint(
to: string,
title: string,
imageUrl: string
): Promise<{ objectId: string }>;
};