์ปคํผ ์๊ฒฉ ์ฃผ๋ฌธ ์ ํ๋ฆฌ์ผ์ด์
๊ฐ์
๊ธฐ์ ์ด ๋ฐ์ ํ๋ฉด์ ์ด์ ๋ ์นดํ์ ๊ฐ์ง ์์๋ ์ง์์ ์ปคํผ๋ฅผ ์ฃผ๋ฌธํ๋ ์๋๊ฐ ๋์์ต๋๋ค. ํ์ง๋ง, ์ค๋งํธํฐ๊ณผ ์น ๊ธฐ๋ฐ์ ์ด๋ฌํ ์ฃผ๋ฌธ ๋ฐฉ์์ ์ฃผ๋ฌธ ์ฒ๋ฆฌ ์์คํ ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด์ฉ์ด ํ์ํ๋ฉฐ ์ด๋ฅผ ๊ตฌ์ถ, ์ด์ฉํ๋ ๋ฐ์๋ ์๋นํ ๊ฐ๋ฐ ์ธ๋ ฅ๊ณผ ๋น์ฉ์ด ๋ญ๋๋ค. ๋ฐ๋ผ์ ์ด๋ฌํ ์จ๋ผ์ธ ์ปคํผ ์ฃผ๋ฌธ ์์คํ ์ ๊ทธ ํธ๋ฆฌํจ์๋ ๋ถ๊ตฌํ๊ณ ์ถฉ๋ถํ ์๋ณธ๊ณผ ์ธ๋ ฅ์ ๊ฐ์ถ ์ผ๋ถ ๋ํ ์ปคํผ ์ ๋ฌธ์ ๋ง์ด ์ด์ํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ ๋ง์ฝ, ๊ณ ์ฑ๋ฅ ํผ๋ธ๋ฆญ ๋ธ๋ก์ฒด์ธ์ผ๋ก ์ด๋ฌํ ์ฃผ๋ฌธ ์์คํ ์ ๋์ฒดํ ์ ์๋ค๋ฉด ์ํฉ์ ์ด๋ป๊ฒ ๋ฌ๋ผ์ง๊น์? ์์ธํ ์นดํ๋ฅผ ํฌํจํด ๋๊ตฌ๋ ์จ๋ผ์ธ ์ปคํผ ์ฃผ๋ฌธ ์์คํ ์ ์๋น์์๊ฒ ์๋น์คํ ์ ์์ ๊ฒ์ ๋๋ค.
์ฌ๊ธฐ์์๋, Kaia ๋ธ๋ก์ฒด์ธ๊ณผ KAS๋ฅผ ํ์ฉํด ์ปคํผ ์ฃผ๋ฌธ์ ์จ๋ผ์ธ์ผ๋ก ๋ฐ๊ณ ๋ ์จ๋ผ์ธ ์ฃผ๋ฌธ ๊ณ ๊ฐ์๊ฒ ์ธ์ผํฐ๋ธ๋ก KAIA๋ฅผ ์ ๊ณตํ ์๋ ์๋ ์ฃผ๋ฌธ ์์คํ ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์๋ดํฉ๋๋ค. ์ฃผ๋ฌธ ๋ด์ญ์ Kaia ๋ธ๋ก์ฒด์ธ์ ์ ์ฅ๋๋ฉฐ ์ฃผ๋ฌธ์ ๋ํ ๋ณด์์ผ๋ก KAIA, NFT, NT ๋ฑ์ ์๋์ผ๋ก ์ ๋ฌํ ์ ์์ต๋๋ค.
์นดํ ์๊ฒฉ ์ฃผ๋ฌธ ์์คํ ์ ์๋์ ๊ฐ์ด 3๊ฐ์ง Application์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
- customer-front: ์นดํ ์ด์ฉ์๊ฐ ์ฌ์ฉํ๋ ๋ชจ๋ฐ์ผ ์น์ฑ์ด๋ฉฐ ์ปคํผ๋ฅผ ์ฃผ๋ฌธํ ์ ์์ต๋๋ค.
- store-front: ์นดํ ์ฃผ์ธ๋ค์ด ์ฌ์ฉํ๋ฉด ๋ชจ๋ฐ์ผ ์น์ฑ์ด๋ฉฐ ๊ฐ๊ฒ ์์น ๋ฑ๋ก, ๋ฉ๋ด ๋ฑ๋ก, ๋ณด์ ์ ์ฑ ๋ฑ๋ก ๋ฑ์ ํ ์ ์์ต๋๋ค.
- service-backend: ์นดํ ์๊ฒฉ ์ฃผ๋ฌธ ํ๋ซํผ์ ์๋น์ค ์ฃผ์ฒด๊ฐ ์ด์ํ๋ ์๋ฒ์ด๋ฉฐ customer-front์ store-front ์ฌ์ฉ์๋ค์ ์์ฒญ์ ์ ๋ฌ๋ฐ์ ๋ธ๋ก์ฒด์ธ์ผ๋ก ์ค๊ฐํ๋ ์ญํ ์ ์ํํฉ๋๋ค. KAS API๋ฅผ ์ฌ์ฉํ๋ ์ฃผ์ฒด์ ๋๋ค.
warning
์ด ํ์ด์ง์ ๋ชจ๋ ๋ด์ฉ์ KAS๋ก ํ์ค์ํ ์ ํ๋ฆฌ์ผ์ด์
๊ฐ๋ฐ์ ๋๋ ์์ ์
๋๋ค.
์์ค์ฝ๋๋ฅผ ํฌํจํ ๋ชจ๋ ๋ด์ฉ์ ์ฌ์ฉ์ ๊ฐ๋ฐ ํ๊ฒฝ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ ์ฉ๋ ์ ์์ต๋๋ค.
์์ค์ฝ๋๋ฅผ ํฌํจํ ๋ชจ๋ ๋ด์ฉ์ ์ฌ์ฉํ๋ ๊ฒ์ ๋ํ ๋ชจ๋ ์ฑ
์์ ์ ์ ์ผ๋ก ์ฌ์ฉ์ ๋ณธ์ธ์๊ฒ ์์ต๋๋ค.
์ด ๋ฌธ์ ํน์ KAS์ ๊ดํ ๋ฌธ์๋ ๊ฐ๋ฐ์ ํฌ๋ผ์ ๋ฐฉ๋ฌธํด ๋์ ๋ฐ์ผ์ญ์์ค.
์์ํ๊ธฐ
์นดํ ์๊ฒฉ ์ฃผ๋ฌธ ์์คํ ์ ๊ตฌ์ฑํ๋ 3๊ฐ์ง Application์ ์์ค์ฝ๋๋ ํ๋์ github repository์์ ํ์ธํ ์ ์์ต๋๋ค. ๋ค์ ๋ช ๋ น์ด๋ฅผ ์ํํ์ฌ ์์ค์ฝ๋๋ฅผ ๋ค์ด๋ก๋ ๋ฐ์ผ์ญ์์ค.
git clone https://github.com/ground-x/kas-bapp-klaybucks.git
๋น๋ ๋ฐ ์คํ
kas-bapp-klaybucks
๋๋ ํ ๋ฆฌ์์ ์๋ ๋ช
๋ น์ด๋ฅผ ํตํด ๊ฐ๊ฐ์ Application์ ๋น๋ํ๊ณ ์คํํ ์ ์์ต๋๋ค.
customer-front
cd customer
npm install
npm start
store-front
cd store
npm install
npm start
service-backend
service-backend๋ฅผ ์คํํ๊ธฐ ์ํด์๋ ๋จผ์ ์ค์ ํ์ผ์ด ํ์ํฉ๋๋ค.
backend/config.template.json
ํ์ผ์ ๋ณต์ฌํ์ฌ backend/config.json
ํ์ผ์ ์์ฑํ ํ ์ ์ ํ ์ค์ ๊ฐ์ ์
๋ ฅํ์ญ์์ค.
-
authorization
: Basic ์ธ์ฆ ๋ฐฉ์์ ์ฌ์ฉํ๋ KAS API ์ธ์ฆ ํค ๋ฅผ ์ ๋ ฅํด ์ฃผ์ญ์์ค. -
kas_account
: KAS Wallet API๋ฅผ ์ฌ์ฉํด ์์ฑํ ๊ณ์ ์ฃผ์ ๋ฅผ ์ ๋ ฅํด ์ฃผ์ญ์์ค. -
master_contract
: (Optional) ์์ ๋ง์ Klaybucks Master Contract๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด Master Contract์ ์ฃผ์๋ฅผ ์ ๋ ฅํด ์ฃผ์ญ์์ค. Master Contract๋ฅผ ๋ฐฐํฌํ๊ธฐ ์ํด์๋backend/storeList.sol
ํ์ผ์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
์ค์ ํ์ผ์ ์์ฑํ ํ์๋ ์๋ ๋ช ๋ น์ด๋ฅผ ํตํด service-backend๋ฅผ ์คํํ์ค ์ ์์ต๋๋ค.
cd backend
go run .
Klaybucks ๋ฐ๋ชจ
์ด ๋ฌธ์ ํน์ KAS์ ๊ดํ ๋ฌธ์๋ ๊ฐ๋ฐ์ ํฌ๋ผ์ ๋ฐฉ๋ฌธํด ๋์ ๋ฐ์ผ์ญ์์ค.
์ ํ๋ฆฌ์ผ์ด์ ์ํฌํ๋ก์ฐ
์ฃผ์ ๊ธฐ๋ฅ
"Klaybucks"๋ ๋ฑ๋ก๋ ์นดํ์ ์์น, ๋ฉ๋ด, ๊ฑฐ๋๋ด์ญ ๋ฑ์ ๋ธ๋ก์ฒด์ธ์ธ Kaia(๊ตฌ Klaytn)์ ๊ธฐ๋กํฉ๋๋ค. ๋ฐ๋ผ์ ์๋น์ค๊ฐ ์ค๋จ๋๋๋ผ๋ ์นดํ ์ฃผ์ธ์ ์๊ฒฉ ์๋น์ค์ ์ฌ์ฉํ ํ์ ๋ฐ์ดํฐ๋ค์ ์ง์์ ์ผ๋ก ์กฐํํ๊ณ ์์ ํ ์ ์์ต๋๋ค. ๋ค์์ Kaia๋ฅผ ์ฌ์ฉํ๋ ์ฃผ์ ๊ธฐ๋ฅ๋ค์ ๋๋ค.
- ์นดํ ์ฃผ์ธ์ ์นดํ์ ์์น, ๋ฉ๋ด ์ ๋ณด ๊ทธ๋ฆฌ๊ณ ๋ณด์ ์ ์ฑ ์ Contract์ ๊ธฐ๋กํฉ๋๋ค.
- ๊ณ ๊ฐ์ ๊ณ์ ์ KAS Wallet API๋ฅผ ์ด์ฉํ์ฌ ์์ฑ๋ฉ๋๋ค.
- ๊ณ ๊ฐ์ Contract์์ Klaybucks ์๋น์ค์ ๋ฑ๋ก๋ ์ฃผ๋ณ ์นดํ๋ค์ ์์น๋ฅผ ํ์ธํฉ๋๋ค.
- ๊ณ ๊ฐ์ ์ฃผ๋ฌธํ ์นดํ๋ฅผ ์ ํํ๊ณ Contract์์ ๋ฉ๋ด์ ๋ณด์ ์ ์ฑ ์ ์กฐํํฉ๋๋ค.
- ๊ณ ๊ฐ์ KakaoPay ๋ฑ ํ ๊ฒฐ์ ์์คํ์ ํตํด ๊ฒฐ์ ๋ฅผ ์๋ฃํ๊ณ ๊ทธ ๋ด์ญ์ Contract์ ๊ธฐ๋กํฉ๋๋ค.
- ์นดํ ์ฃผ์ธ์ ์์ ์ Contract์ ๋ฑ๋ก๋ ๊ฒฐ์ ๋ด์ญ์ ํ์ธํ๊ณ ์ฃผ๋ฌธ์ ์น์ธ/๊ฑฐ๋ถํฉ๋๋ค.
์์ ๊ธฐ๋ฅ๋ค์ Kaia๋ฅผ Application์ Storage ์ค ํ๋๋ก์ ์ฌ์ฉํ๋ฉฐ, ๋ฐ์ดํฐ๋ฅผ ์ฐ๊ณ ์ฝ๋ ํ์๋ฅผ ์์ํ๋ ๋ด์ฉ์ ๋๋ค. ๋ฐ๋ผ์, ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ Contract๋ฅผ ์์ฑํ๊ณ Contract ๋ด๋ถ์ ์ ์ ํ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ์กฐ๋ฅผ ์ ์ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
store-front์์๋ ๊ฐ ์นดํ์ ์ฃผ์ธ๋ค์ด ์นดํ์ ๋ฉ๋ด์ ์ฃผ๋ฌธ๋ด์ญ์ ์ ์ฅํ๋ Store Contract๋ฅผ ๊ฐ๊ฐ ๋ฐฐํฌํฉ๋๋ค. Store Contract์๋ ์๋์ ๊ฐ์ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ๊ฐ์ ์ ์ฅํ๊ณ ์กฐํํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ฉฐ ๊ฐ ์นดํ์ ์ฃผ์ธ์ด Contract Owner๊ฐ ๋ฉ๋๋ค.
enum MenuStatus {activated, deactivated}
enum OrderStatus {ordered, approved, denied}
enum RewardPolicyStatus {activated, deactivated}
struct Menu {
string name;
uint32 price;
uint256 reward; // digit: peb
MenuStatus status;
}
struct Order {
address payable customer;
string content;
uint32 price;
OrderStatus status;
}
event OrderReciept(
uint32 indexed _id,
address indexed _from,
string _paymentTxId,
string _content,
uint32 _price
);
์์ ๊ณผ์ ์ ํตํ ๋ฐฐํฌ๋ ๊ฐ๊ฐ์ Store Contract๋ค์ Master Contract์ ๋ฑ๋ก๋ฉ๋๋ค. Master Contract๋ Klaybucks ์๋น์ค ์ด์์๊ฐ Owner๊ฐ ๋๋ฉฐ, ์๋์ ๊ฐ์ ํํ๋ก ์นดํ์ ์์น์ ๊ฐ Store Contract์ ์ฃผ์๋ฅผ ์ ์ฅํฉ๋๋ค. Store ๋ฐ์ดํฐ๋ Map ๋๋ List ํํ๋ก ๊ด๋ฆฌ๋์ด ๊ณ ๊ฐ๋ค์ด ์ง๋์์ ๋ฑ๋ก๋ ์นดํ๋ค์ ํ๋ฒ์ ์กฐํํ ์ ์๋๋ก ๋ชจ์์ฃผ๋ ์ญํ ์ ํฉ๋๋ค.
enum Status {activated, deactivated}
struct Store {
string name;
string locationX;
string locationY;
address storeOwner;
address storeContract;
Status status;
}
Klaybucks์์ ๋ ์์ ๊ฐ์ด ๊ตฌํ๋ Contract๋ฅผ ์ฝ๊ณ ๋น ๋ฅด๊ฒ ์ฌ์ฉํ๊ธฐ ์ํด์ KAS API๋ฅผ ์ด์ฉํ์์ต๋๋ค. ์๋ ๋ด์ฉ์ ์๋น์ค๋ฅผ ๊ตฌ์ฑํ๋ ์ฃผ์ํ ๊ธฐ๋ฅ์ ๋ํ ์ค๋ช ๊ณผ ์ฝ๋ ์์์ ๋๋ค(์์ธํ ๊ตฌํ ์ฝ๋๋ ๋ค์ github์์ ํ์ธ ๊ฐ๋ฅ).
*KAS API๋ service-backend์์ ํธ์ถํ๋ฉฐ, customer-front๋ store-front์์๋ ํ์ํ ํ๋ผ๋ฏธํฐ ๊ฐ์ ์ฌ์ฉ์์๊ฒ ์ ๋ ฅ๋ฐ์ service-backend๋ก ์ ๋ฌํฉ๋๋ค. ์ด๋ฌํ ์ค๊ณ๋ KAS ์ฌ์ฉ์ ํ์ํ API Key๋ฅผ ๋์ค์๊ฒ ๋ ธ์ถ์ํค์ง ์๊ธฐ ์ํจ์ ๋๋ค.
์ด ๋ฌธ์ ํน์ KAS์ ๊ดํ ๋ฌธ์๋ ๊ฐ๋ฐ์ ํฌ๋ผ์ ๋ฐฉ๋ฌธํด ๋์ ๋ฐ์ผ์ญ์์ค.
์นดํ Contract ์์ฑ
Klaybucks์์๋ ๋ ์ข ๋ฅ์ Contract๊ฐ ์ฌ์ฉ๋ฉ๋๋ค. ํ๋๋ Master Contract์ด๋ฉฐ ์๋น์ค ์์ ์ ๋จ ํ๋ฒ๋ง ๋ฐฐํฌ๋ฉ๋๋ค. ์ด Contract์๋ Klaybucks์ ์ํ๋ ์นดํ๋ค์ ์์น์ ์ด๋ฆ์ด ์ ์ฅ๋ฉ๋๋ค. ๋ ๋ค๋ฅธ ์ข ๋ฅ์ Contract๋ Store Contract๋ผ ๋ถ๋ฆฌ์ฐ๋ฉฐ, ๊ฐ๊ฐ์ ์นดํ๊ฐ Klaybucks์ ๋ฑ๋ก ์์ ์์ ๋ง์ Store Contract๋ฅผ ๋ฐฐํฌํ๊ฒ ๋ฉ๋๋ค. ์ด Contract์์๋ ํด๋น ์นดํ์ ๋ฉ๋ด, ๋ณด์์ ์ฑ , ๊ทธ๋ฆฌ๊ณ ๊ฑฐ๋๋ด์ญ์ด ๊ธฐ๋ก๋๊ฒ ๋ฉ๋๋ค.
Klaybucks์์๋ Contract๋ฅผ ๋ฐฐํฌํ๊ธฐ ์ํด์ Wallet API ์ค ์ค๋งํธ ์ปจํธ๋ํธ ๋ฐฐํฌํ๊ธฐ: ๋ค๋ฅธ ๊ณ์ ์ด ํธ๋์ญ์ ์ ์ก ์์๋ฃ๋ฅผ ๋์ ๋ถ๋ดํ๊ธฐ๋ฅผ ์ฌ์ฉํ์์ต๋๋ค. ์๋ ์ฝ๋๋ golang์ผ๋ก ๊ตฌํ๋ service-backend์์์ Store Contract ๋ฐฐํฌ ์์์ ๋๋ค(์ค์ ์ฌ์ฉ๋ ์ฝ๋๋ github์์ ํ์ธ ๊ฐ๋ฅ). ์์์์๋ ํด๋น KAS API ์ฌ์ฉ์ ์ํด ํ์ํ Store Contract์ Bytecode์ ABI๊ฐ ์ด๋ฏธ String ํํ๋ก ์ ์๋์ด ์๋ค๊ณ ๊ฐ์ ํ์์ต๋๋ค. Store Contract๋ constructor ํจ์๊ฐ ์คํ ์์ input parameter๋ก Store์ Name๊ณผ Owner๋ฅผ ์ ๋ ฅ๋ฐ๋๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ์์์์๋ ์ด ๊ฐ์ ์ฌ์ฉ์๋ก๋ถํฐ ์ ๋ ฅ๋ฐ์ ํ, ํด๋น KAS API ์ฌ์ฉ์ ์ ํฉํ ํํ๋ก ๋ณํํ์ฌ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
// DeployStoreContract handles Store Contract deploying requests with the given store name and the owner address.
func DeployStoreContract(c *gin.Context) {
url := "https://wallet-api.klaytnapi.com/v2/tx/fd/contract/deploy"
params := struct {
Name string `form:"name" json:"name"`
Owner string `form:"owner" json:"owner"`
}{}
// Step 1. Get user inputs from the request
if err := c.ShouldBindJSON(¶ms); err != nil {
c.String(-1, "invalid input data" + err.Error())
return
}
// Step 2. Prepare field values of contract deploy transaction.
// STORE_CONTRACT_ABI and STORE_CONTRACT_CODE are generated from the solidity code of Store Contract.
_abi , err := abi.JSON(bytes.NewBufferString(STORE_CONTRACT_ABI))
if err != nil {
c.String(-1, err.Error())
return
}
// Pack function convert user inputs to the rlp-encoded constructor parameter.
inputParam, err := _abi.Constructor.Inputs.Pack(params.Name, common.HexToAddress(params.Owner))
if err != nil {
c.String(-1, err.Error())
return
}
// input format of the KAS API, /v2/tx/fd/contract/deploy
bodyData, err := json.Marshal(&kasDeployTxFD{
From: KAS_ACCOUNT,
Value: "0x0",
GasLimit: 8000000,
Input: STORE_CONTRACT_CODE + Encode(inputParam),
Submit: true,
})
if err != nil {
c.String(-1, err.Error())
return
}
// Step 3. Call a KAS API with parameters and HTTP headers.
// "Authorization" header should be set with your KAS API Key (e.g., "Basic xxxx...") .
// "x-krn" header should indicate your KAS Wallet resource (e.g., "krn:1001:wallet:80:account:default").
req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyData))
if err != nil {
c.String(-1, err.Error())
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", KAS_API_KEY)
req.Header.Add("x-krn", KAS_WALLET_KRN)
res, err := httpClient.Do(req)
if err != nil {
c.String(-1, err.Error())
return
}
// Step 4. Read and return back to the user the response of KAS.
bodyContents, err := ioutil.ReadAll(res.Body)
if err != nil {
c.String(-1, err.Error())
return
}
_ = res.Body.Close()
c.String(200, string(bodyContents))
}
์ด ๋ฌธ์ ํน์ KAS์ ๊ดํ ๋ฌธ์๋ ๊ฐ๋ฐ์ ํฌ๋ผ์ ๋ฐฉ๋ฌธํด ๋์ ๋ฐ์ผ์ญ์์ค.
์นดํ ๋ฉ๋ด/๋ณด์ ๋ฑ๋ก๊ณผ Klaybucks ์๋น์ค ๋ฑ๋ก
์นดํ ์ฃผ์ธ์ ์์ ์ด ๋ฐฐํฌํ ์นดํ Contract์ ์ฃผ์์ ์์น์ ๋ณด๋ฅผ Master Contract์ ๋ฑ๋กํ์ฌ Klaybucks ์๋น์ค์ ๋ฑ๋กํ ์ ์์ต๋๋ค. ๊ณ ๊ฐ๋ค์ Klaybucks์ ๋ฑ๋ก๋ ์นดํ๋ฅผ ์กฐํํ ๋ ๊ฐ์ฅ ๋จผ์ ์กฐํํ๋ Contract์ ๋๋ค.
Klaybucks์์๋ Contract๋ฅผ ์คํํ๊ธฐ ์ํด์ Wallet API ์ค ์ค๋งํธ ์ปจํธ๋ํธ ์คํํ๊ธฐ: ๋ค๋ฅธ ๊ณ์ ์ด ํธ๋์ญ์ ์ ์ก ์์๋ฃ๋ฅผ ๋์ ๋ถ๋ดํ๊ธฐ๋ฅผ ์ฌ์ฉํ์์ต๋๋ค. ์๋ ์ฝ๋๋ golang์ผ๋ก ๊ตฌํ๋ service-backend์์์ Master Contract ์คํ ์์์ ๋๋ค(์ค์ ์ฌ์ฉ๋ ์ฝ๋๋ github์์ ํ์ธ ๊ฐ๋ฅ). ์์์์๋ ํด๋น KAS API ์ฌ์ฉ์ ์ํด ํ์ํ Master Contract์ ABI๊ฐ ์ด๋ฏธ String ํํ๋ก ์ ์๋์ด ์๋ค๊ณ ๊ฐ์ ํ์์ต๋๋ค.
// AddStoreInfo registers a new store to Master Contract
func AddStoreInfo(c *gin.Context) {
url := "https://wallet-api.klaytnapi.com/v2/tx/fd/contract/execute"
params := struct {
Name string `json:"name"`
X string `json:"x"`
Y string `json:"y"`
Owner string `json:"owner"`
ContractAddr string `json:"contract_addr"`
}{}
// Step 1. Get user inputs from the request
if err := c.ShouldBindJSON(¶ms); err != nil {
c.String(-1, "invalid input data" + err.Error())
return
}
// Step 2. Prepare field values of contract execution transaction.
// MASTER_CONTRACT_ABI is generated from the solidity code of Store Contract.
_abi , err := abi.JSON(bytes.NewBufferString(MASTER_CONTRACT_ABI))
if err != nil {
c.String(-1, err.Error())
return
}
// Pack function convert user inputs to the rlp-encoded parameter.
// "addStore" is a function of Master Contract.
inputParam, err := _abi.Pack("addStore", params.Name, params.X, params.Y, common.HexToAddress(params.Owner), common.HexToAddress(params.ContractAddr))
if err != nil {
c.String(-1, err.Error())
return
}
// input format of the KAS API, /v2/tx/fd/contract/execute
bodyData, err := json.Marshal(&kasExecuteTxFD{
From: KAS_ACCOUNT,
To: MasterContract,
Value: "0x0",
GasLimit: 8000000,
Input: Encode(inputParam),
Submit: true,
})
if err != nil {
c.String(-1, err.Error())
return
}
// Step 3. Call a KAS API with parameters and HTTP headers.
// "Authorization" header should be set with your KAS API Key (e.g., "Basic xxxx...") .
// "x-krn" header should indicate your KAS Wallet resource (e.g., "krn:1001:wallet:80:account:default").
req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyData))
if err != nil {
c.String(-1, err.Error())
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", KAS_API_KEY)
req.Header.Add("x-krn", KAS_WALLET_KRN)
res, err := httpClient.Do(req)
if err != nil {
c.String(-1, err.Error())
return
}
// Step 4. Read and return back to the user the response of KAS.
bodyContents, err := ioutil.ReadAll(res.Body)
if err != nil {
c.String(-1, err.Error())
return
}
_ = res.Body.Close()
c.String(200, string(bodyContents))
}
๋์ผํ API๋ฅผ ํ์ฉํ์ฌ ์นดํ ์ฃผ์ธ์ ์์ ์ Contract์ ๋ฉ๋ด์ ๋ณด์ ์ ์ฑ
์ ๋ฑ๋กํ๊ฑฐ๋ ์์ ํ ์๋ ์์ต๋๋ค.
์ ์์์ ๋น์ทํ ์ฌ์ฉ์ ํตํ์ฌ ์ธ์ ๋ ์์ ์ ์นดํ์ ์ด์ ์ํ๋ฅผ ์ค์ค๋ก ์
๋ฐ์ดํธ ํ ์ ์์ต๋๋ค.
์ด ๋ฌธ์ ํน์ KAS์ ๊ดํ ๋ฌธ์๋ ๊ฐ๋ฐ์ ํฌ๋ผ์ ๋ฐฉ๋ฌธํด ๋์ ๋ฐ์ผ์ญ์์ค.
Transaction Receipt ํ์ธ
์์ฑ๋ Transaction์ด ๋ธ๋ก์ ๋ด๊ธฐ๋ฉด Transaction Receipt์ ์์ฑํ๊ฒ ๋๊ณ ์ด ์๊ฐ์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋ฉ๋๋ค. ๋ฐ๋ผ์, Transaction์ ๋ฐฐํฌํ ํ์๋ ๋ฐ๋์ Transaction Receipt์ ํ์ธํ์ฌ ์ฒ๋ฆฌ๋ ๊ฒฐ๊ณผ๊ฐ์ ํ์ธํด์ผ ํฉ๋๋ค.
Klaybucks์์๋ Transaction Receipt์ ํ์ธํ๊ธฐ ์ํด KAS Node API๋ฅผ ์ฌ์ฉํฉ๋๋ค. KAS Node API๋ฅผ ์ฌ์ฉํ๋ฉด ๋
ธ๋์์ ์ ๊ณตํ๋ API๋ค์ ๋์ผํ ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ ์ฝ๋๋ golang์ผ๋ก ๊ตฌํ๋ service-backend์์ klay_getTransactionReceipt
method๋ฅผ ์ฌ์ฉํ์ฌ Transaction Receipt์ ํ์ธํ๋ ์์์
๋๋ค.
func GetTxReceipt(c *gin.Context) {
url := "https://node-api.klaytnapi.com/v2/klaytn"
params := struct {
TxHash string `form:"tx_hash" json:"tx_hash"`
}{}
// Step 1. Get user inputs from the request
if err := c.ShouldBindQuery(¶ms); err != nil {
c.String(-1, "invalid input data" + err.Error())
return
}
// Step 2. Prepare a Klaytn node method and parameters to use KAS Node API.
// You can see the detailed information of node methods from
// https://ko.docs.klaytn.foundation/dapp/json-rpc/api-references
bodyData, _ := json.Marshal(&kasKlaytnRPC{
JsonRpc: "2.0",
Method: "klay_getTransactionReceipt",
Params: []string{params.TxHash},
Id: 1,
})
// Step 3. Call a KAS API with parameters and HTTP headers.
// "Authorization" header should be set with your KAS API Key (e.g., "Basic xxxx...") .
// "x-krn" header should indicate your KAS Node resource (e.g., "krn:1001:node").
req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyData))
if err != nil {
c.String(-1, err.Error())
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", KAS_API_KEY)
req.Header.Add("x-krn", KAS_NODE_KRN)
res, err := httpClient.Do(req)
if err != nil {
c.String(-1, err.Error())
return
}
// Step 4. Read and return back to the user the response of KAS.
bodyContents, err := ioutil.ReadAll(res.Body)
if err != nil {
c.String(-1, err.Error())
return
}
_ = res.Body.Close()
c.String(200, string(bodyContents))
}
Transaction Receipt์ ํ์ธํ ๋ ์ฃผ์ํด์ผํ ์ ์ Receipt์ด ์์ฑ๋๋ ์๊ฐ์ด deterministicํ์ง ์๋ค๋ ์ ์ ๋๋ค. Kaia ๋คํธ์ํฌ๋ ๋น ๋ฅธ ๋ธ๋ญ์์ฑ ๋ฅ๋ ฅ์ ๊ฐ์ง๊ณ ์๊ธฐ์ ์ผ๋ฐ์ ์ผ๋ก 1์ด๋ด์ Transaction์ด ์ฒ๋ฆฌ๋ ์ ์์ง๋ง, ๊ทธ ์๊ฐ์ ๋คํธ์ํฌ์ ์ง์ฐ์ด๋ Transaction ํผ์ก๋์ ๋ฐ๋ผ ์ง์ฐ๋ ์ ์์ต๋๋ค. ์ด๋ฌํ ์ด์ ๋ก Receipt์ ํ์ธํ๋ ค๋ ์๊ฐ์ ์์ง Transaction์ด ์ฒ๋ฆฌ๋์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ์๋ Transaction์ด ์ฒ๋ฆฌ๋ ๋๊น์ง ๋ฐ๋ณต์ ์ผ๋ก Receipt์ ์์ฒญํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
Klaybucks์์๋ ์๋์ ๊ฐ์ด front ์ฝ๋์์ Receipt์ ๋ฐ๋ณต์ ์ผ๋ก ์์ฒญํฉ๋๋ค. ๋ฌผ๋ก , golang์ผ๋ก ๊ตฌํ๋ service-backend์์ ๋ฐ๋ณต loop์ ์ถ๊ฐํ ์๋ ์์๋ง ๊ทธ๋ฌํ ๊ฒฝ์ฐ์๋ ์ฌ์ฉ์ ์์ฒญ์ ๋ํ ์๋ต์ด blocking ๋ ์ ์์ต๋๋ค. ์ด๋ฅผ ํผํ๊ธฐ ์ํด์ Klaybucks์์๋ ์๋ ์์์ฒ๋ผ javascript๋ก ๊ตฌํ๋ front ์ฝ๋์์ receipt์ ํ์ธํ๊ณ ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ ๋ฐ๋ณต์ ์ผ๋ก ์์ฒญํ๋ ๋ก์ง์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
function CheckTransactionReceipt(txHash) {
let self = this;
function getReceipt(txHash) {
axios
.get(klaybucks_backend + "/klaytn/receipt?tx_hash=" + txHash)
.then((response) => {
let result = response.data["result"];
if (result !== null) {
clearInterval(self.intervalId2);
// check the result and process next steps
}
})
.catch((error) => {
console.log(error);
});
}
self.intervalId2 = setInterval(getReceipt, 1000, txHash);
setTimeout(clearInterval, 10000, self.intervalId2);
}
์ด ๋ฌธ์ ํน์ KAS์ ๊ดํ ๋ฌธ์๋ ๊ฐ๋ฐ์ ํฌ๋ผ์ ๋ฐฉ๋ฌธํด ๋์ ๋ฐ์ผ์ญ์์ค.
๊ณ ๊ฐ ๊ณ์ ์์ฑ
Klaybucks์์ ์นดํ ๊ณ ๊ฐ์ ์ํ customer-front๋ฅผ ์ด์ฉํ๊ธฐ ์ํด์๋ Kaia Account๋ฅผ ์์ฑํด์ผํฉ๋๋ค. ๊ณ ๊ฐ์ด ์นดํ ๋ฉ๋ด๋ฅผ ์ฃผ๋ฌธํ๋ ๊ฒฝ์ฐ์๋ ์ด Account๋ก๋ถํฐ Transaction์ด ๋ฐ์ํ๊ฒ๋๊ณ , ์นดํ๋ก๋ถํฐ KAIA ๋ฑ์ ๋ณด์์ผ๋ก ๋ฐ๋ ๊ฒฝ์ฐ์๋ ์ด Account ์ฃผ์๋ก ๋ฐ๊ฒ๋ฉ๋๋ค. ์ฆ, Klaybucks์์ ๋ฐ์ํ๋ ๊ณ ๊ฐ์ ํ์๋ค์ ์ด Account์ ๊ท์๋์ด ์ ์ฅ๋๊ฒ ๋ฉ๋๋ค.
์์ ๊ฐ์ด Klaybucks ์๋น์ค์ ๊ณ์ ์ Kaia Account๋ก ๋์ฒดํ๋ ๊ฒฝ์ฐ์๋ ์ฌ์ฉ์๊ฐ Private Key๋ฅผ ๊ด๋ฆฌํด์ผํ๋ค๋ ๋ถ๋ด์ด ์กด์ฌํฉ๋๋ค. ์ฌ์ฉ์๋ ๋ณต์กํ ํํ์ Private Key๋ฅผ ๋ณด๊ดํ๊ณ ๊ด๋ฆฌํด์ผํ๋ฉฐ, ๋ถ์คํ๋ ๊ฒฝ์ฐ์๋ ๋์ฐพ์ ๋ฐฉ๋ฒ์ด ์๊ฒ ๋ฉ๋๋ค. ์ด๋ฌํ ๋ธ๋ก์ฒด์ธ์ ๋ถํธํ ์ฌ์ฉ์ฑ์ ๊ฐ์ ํ๊ธฐ ์ํด Klaybucks์์๋ KAS์ Wallet API๋ฅผ ์ด์ฉํ์ฌ ์นดํ ๊ณ ๊ฐ๋ค์ Private Key ๊ด๋ฆฌ๋ฅผ ์์ํ์์ต๋๋ค. ์๋ ์ฝ๋๋ golang์ผ๋ก ๊ตฌํ๋ service-backend์์์ ๊ณ ๊ฐ ๊ณ์ ์ ์์ฑ์ ์ํด KAS Wallet API ์ค ๊ณ์ ์์ฑํ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ ๋ด์ฉ์ ๋๋ค.
func CreateAccount(c *gin.Context){
url := "https://wallet-api.klaytnapi.com/v2/account"
// Step 1. Call a KAS API with parameters and HTTP headers.
// "Authorization" header should be set with your KAS API Key (e.g., "Basic xxxx...") .
// "x-krn" header should indicate your KAS Wallet resource (e.g., "krn:1001:wallet:80:account:default").
req, err := http.NewRequest("POST", url, nil)
if err != nil {
c.String(-1, err.Error())
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", KAS_API_KEY)
req.Header.Add("x-krn", KAS_NODE_KRN)
res, err := httpClient.Do(req)
if err != nil {
c.String(-1, err.Error())
return
}
// Step 2. Read and return back to the user the response of KAS.
bodyContents, err := ioutil.ReadAll(res.Body)
if err != nil {
c.String(-1, err.Error())
return
}
_ = res.Body.Close()
c.String(200, string(bodyContents))
}
์ด ๋ฌธ์ ํน์ KAS์ ๊ดํ ๋ฌธ์๋ ๊ฐ๋ฐ์ ํฌ๋ผ์ ๋ฐฉ๋ฌธํด ๋์ ๋ฐ์ผ์ญ์์ค.
์นดํ ์ ๋ณด ์กฐํ
๊ณ์ ์ ์์ฑํ ๊ณ ๊ฐ์ Klaybucks์ ๋ฑ๋ก๋ ์นดํ๋ค์ ์ ๋ณด๋ฅผ ์กฐํํ๊ธฐ ์ํ์ฌ Master Contract์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์ต๋๋ค. ๊ทธ ์ค ์์ ์ด ์ด์ฉํ๊ณ ์ถ์ ์นดํ๋ฅผ ์ ํํ๋ฉด ๋์ผํ ๋ฐฉ์์ ์ฌ์ฉํ์ฌ ํด๋น ์นดํ์ ๋ฉ๋ด์ ๋ณด๋ฅผ ์ถ๊ฐ๋ก ์ฝ์ด์ต๋๋ค.
Klaybucks์์๋ Contract ์ค ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ํจ์๋ฅผ ์คํํ๊ธฐ ์ํด Node API๋ฅผ ์ฌ์ฉํ์์ต๋๋ค. ์๋ ์ฝ๋๋ golang์ผ๋ก ๊ตฌํ๋ service-backend์์์ Master Contract ์คํ ์์์ ๋๋ค(์ค์ ์ฌ์ฉ๋ ์ฝ๋๋ github์์ ํ์ธ ๊ฐ๋ฅ). ์์์์๋ ํด๋น KAS API ์ฌ์ฉ์ ์ํด ํ์ํ Master Contract์ ABI๊ฐ ์ด๋ฏธ String ํํ๋ก ์ ์๋์ด ์๋ค๊ณ ๊ฐ์ ํ์์ต๋๋ค.
func GetStores(c *gin.Context) {
url := "https://node-api.klaytnapi.com/v2/klaytn"
// Step 1. Prepare "klay_call" API among Node APIs
bodyData, _ := json.Marshal(&kasKlaytnRPC{
JsonRpc: "2.0",
Method: "klay_call",
Params: []interface{}{
structCall{
From: KASAccount,
To: MASTER_CONTRACT,
Gas: "0x7A1200", // 8000000
Data: MASTER_CONTRACT_ABI_GETSTORE,
},
"latest",
},
Id: 1,
})
// Step 2. Call a KAS API with parameters and HTTP headers.
// "Authorization" header should be set with your KAS API Key (e.g., "Basic xxxx...") .
// "x-krn" header should indicate your KAS Node resource (e.g., "krn:1001:node").
req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyData))
if err != nil {
c.String(-1, err.Error())
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", KAS_API_KEY)
req.Header.Add("x-krn", KAS_NODE_KRN)
res, err := httpClient.Do(req)
if err != nil {
c.String(-1, err.Error())
return
}
bodyContents, err := ioutil.ReadAll(res.Body)
if err != nil {
c.String(-1, err.Error())
return
}
res.Body.Close()
// Step 3. Parse the return of Node API
var rpcRet struct {
JsonRPC string `json:"jsonrpc"`
Id int `json:"id"`
Result string `json:"result"`
}
if err := json.Unmarshal(bodyContents, &rpcRet); err != nil {
c.String(-1, err.Error())
return
}
// Step 4. Parse the store information which is stored as an array in the Contract
_abi , err := abi.JSON(bytes.NewBufferString(MASTER_CONTRACT_ABI))
if err != nil {
c.String(-1, err.Error())
return
}
var storeArray []struct {
Name string `json:"name"`
X string `json:"x"`
Y string `json:"y"`
StoreOwner common.Address `json:"store_owner"`
StoreContract common.Address `json:"store_contract"`
Status uint8 `json:"status"`
}
if err := _abi.Unpack(&storeArray, "getStores", hexutil.MustDecode(rpcRet.Result)); err != nil {
c.String(-1, err.Error())
return
}
c.JSON(200, storeArray)
}
๊ณ ๊ฐ์ ์์๊ฐ์ด ๋ฑ๋ก๋ ์นดํ๋ค์ ์กฐํํ ํ, ์ํ๋ ์นดํ ๋ฉ๋ด๋ฅผ ์ถ๊ฐ ์กฐํํ๊ธฐ ์ํด ๋์ผํ API๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์ดํ ๋ฉ๋ด๋ฅผ ์ ํํ๊ณ ์ฃผ๋ฌธ์ ํ๋ฉด 2. ์นดํ ๋ฉ๋ด/๋ณด์ ๋ฑ๋ก๊ณผ Klaybucks ์๋น์ค ๋ฑ๋ก
์์ ์ฌ์ฉ๋ API๋ฅผ ์ฌ์ฉํ์ฌ ๋์ผํ ๋ฐฉ์์ผ๋ก ์ฃผ๋ฌธ๋ด์ญ์ Contract์ ๋ฑ๋กํฉ๋๋ค. ๋ฌผ๋ก , ๋ฑ๋ก๋ Transaction์ Receipt์ ํ์ธํ๊ธฐ ์ํด 3. Transaction Receipt ํ์ธ
์ ์ธ๊ธ๋ ๋ด์ฉ๋ ์ํํ์ฌ์ผ ํฉ๋๋ค.
์ด ๋ฌธ์ ํน์ KAS์ ๊ดํ ๋ฌธ์๋ ๊ฐ๋ฐ์ ํฌ๋ผ์ ๋ฐฉ๋ฌธํด ๋์ ๋ฐ์ผ์ญ์์ค.
์ฃผ๋ฌธ ๋ด์ญ ์กฐํ
๊ณ ๊ฐ์ด ํน์ ์นดํ์ Store Contract์ ์ฃผ๋ฌธ๋ด์ญ์ ๋ฑ๋กํ๊ฒ ๋๋ฉด, ์นดํ ์ฃผ์ธ์ Kaia Block ๋ด์ ํฌํจ๋ Receipt์ ์กฐํํ์ฌ ์ด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์, ์นดํ ์ฃผ์ธ์ ๋งค ๋ธ๋ญ๋ง๋ค ์์ ์ Store Contract๋ก๋ถํฐ ์์ฑ๋ Receipt์ด ์๋์ง๋ฅผ ์ง์์ ์ผ๋ก ํ์ธํ์ฌ์ผํฉ๋๋ค.
// Retrieves the receipt logs of the given contract address
RetrieveLogs = (address) => {
let self = this;
function getReceipt(address) {
let hexBlockNumber = caver.utils.toHex(self.state.blockNumber);
axios
.get(
klaybucks_backend +
"getReceipts?from_block=" +
hexBlockNumber +
"&to_block=" +
hexBlockNumber +
"&contract=" +
address
)
.then((response) => {
self.setState((current) => ({
blockNumber: self.state.blockNumber + 1,
}));
if (response.data !== null) {
let current_receipts = response.data;
// Add validation logic for the receipts
current_receipts.map((item) => {
let order = qs.parse(item.content);
let receipt = {
index: item.index,
order: JSON.stringify(order),
price: item.price,
reward: self.CalculateReward(order),
tid: item.payment_tx_id,
status: "ordered",
};
self.setState((current) => ({
receipts: self.state.receipts.concat(receipt),
}));
return item;
});
}
})
.catch((error) => {
console.log(error);
});
}
self.intervalId = setInterval(getReceipt, 1000, address);
};
์์ ๊ณผ์ ์ ํตํด ์ฃผ๋ฌธ ๋ด์ญ์ด ํ์ธ๋๋ฉด ์นดํ ๋ฉ๋ด/๋ณด์ ๋ฑ๋ก๊ณผ Klaybucks ์๋น์ค ๋ฑ๋ก
์ Transaction Receipt ํ์ธ
์์ ์ธ๊ธ๋ API๋ฅผ ์ฌ์ฉํ์ฌ ์ฃผ๋ฌธ์ ์ํ ๊ฐ์ "์น์ธ" ๋๋ "๊ฑฐ๋ถ"๋ก ์
๋ฐ์ดํธํฉ๋๋ค. ์ด๋ ๊ฒ ์
๋ฐ์ดํธ๋ ์ฃผ๋ฌธ ์ํ๋ด์ญ์ ๊ณ ๊ฐ์ธก์์๋ ๋์ผํ ๊ณผ์ ์ ํตํ์ฌ ์กฐํํ ํ ์ต์ข
์ ์ผ๋ก ์ฃผ๋ฌธ์ด ๋ฐ์๋ค์ฌ์ก์์ ํ์ธํ ์ ์์ต๋๋ค.
์ด ๋ฌธ์ ํน์ KAS์ ๊ดํ ๋ฌธ์๋ ๊ฐ๋ฐ์ ํฌ๋ผ์ ๋ฐฉ๋ฌธํด ๋์ ๋ฐ์ผ์ญ์์ค.