본문으로 건너뛰기

Swap 기능 구현하기

Swap 기능을 통해 CW20 토큰 ()과 XPLA ()를 교환하거나, 혹은 CW20 토큰끼리도 쉽게 교환할 수 있습니다.

Break The Bricks 게임에서 사용하는 CW20 토큰과 XPLA를 교환하는 Swap을 하기 위해서는 Pair가 필요합니다. Swap의 실제 기능은 Web3 Gaming Ops에서 체험해 볼 수 있습니다. 이번 단계에서는 CW20 토큰과 XPLA 간 Swap 기능을 어떻게 구현하는지 살펴보겠습니다.

Pair란 무엇인가요?

Pair란 Swap을 위해 토큰과 코인들을 모아둔 스마트 컨트랙트를 의미합니다. Alice가 A 코인을 B 코인으로 교환하는 Swap을 Pair 컨트랙트에 요청한다고 가정합시다. Pair 컨트랙트는 Alice에게 A 코인을 받는 대신, Pair 컨트랙트 내에 있는 B 코인을 Alice에게 전달합니다. 위와 같은 방식으로 Swap이 진행되고, 이를 위해선 Pair가 꼭 필요합니다.

정보

이번 단계에서 Pair를 만들기 위해 Dezswap Contract를 사용하였습니다. Dezswap Contract는 Automated Market-Maker(AMM) 프로토콜을 이용합니다. 더 자세한 정보는 Dezswap Docs를 참고해보세요. 여러분들은 새로 Contract를 개발하여 Pair를 만드셔도 좋고, 예제처럼 Dezswap을 이용하셔도 좋습니다.

목차​

다음과 같은 절차로 Swap 기능을 구현해보겠습니다.

  1. CW20 토큰 준비

  2. Pair 생성

  3. Increase Allowance

  4. 유동성 공급 (Provide Liquidity)

  5. Swap 진행 (Execute Swap)

0. CW20 토큰 준비​

먼저 예제에서 사용할 ACAD란 이름의 CW20 토큰을 생성하였습니다. 컨트랙트 주소는 xpla13mxqv7yqedcpm67fw7yz7p4s32rlsdkaae3ecmc0tram72ewefts2uu3ax입니다.

CW20 토큰을 생성하는 Javascript 코드는 아래와 같습니다. 토큰(CW20) 발행 및 전송하기 (Javascript) 단계에서 사용한 코드 중 name과 symbol만 수정하였습니다.

const { LCDClient, MnemonicKey, MsgInstantiateContract } = require("@xpla/xpla.js");

const lcd = new LCDClient({
chainID: 'cube_47-5',
URL: 'https://cube-lcd.xpla.dev'
});

const main = async () => {
const mk = new MnemonicKey({
mnemonic: 'myth snow ski simple century dad gun dolphin sail lawsuit fringe image toast betray frown keep harbor flash table prevent isolate panic tag vehicle' // 여러분의 니모닉 단어로 바꿔주세요.
})

const wallet = lcd.wallet(mk);
const myWalletAddress = wallet.key.accAddress;

const init_msg = {
name: "CW20 Contract for Swap Tutorial",
symbol: "ACAD",
decimals: 6,
initial_balances: [{ address: myWalletAddress, amount: "2000000000000000" }],
mint: {
minter: myWalletAddress
}
};

const instantiate = new MsgInstantiateContract(
myWalletAddress,
myWalletAddress,
1,
init_msg,
{},
'Example for Swap',
);

const signedTx = await lcd.wallet(mk).createAndSignTx({
msgs: [instantiate]
});

const txResult = await lcd.tx.broadcastSync(signedTx);
console.log(txResult.txhash);
}
main()

1. Pair 생성​

예제에서는 Dezswap의 dezswap_factory 컨트랙트를 실행시켜 Pair를 생성할 것입니다. factory 컨트랙트는 토큰 Pair를 관리하는 기능을 수행합니다. dezswap_factory 컨트랙트 주소는 Mainnet의 경우 xpla1j33xdql0h4kpgj2mhggy4vutw655u90z7nyj4afhxgj4v5urtadq44e3vd이고, Testnet은 xpla1j4kgjl6h4rt96uddtzdxdu39h0mhn4vrtydufdrk4uxxnrpsnw2qug2yx2입니다.

dezswap_factory 컨트랙트의 create_pair 함수를 Execute하는 Javascript 코드는 아래와 같습니다.

const { LCDClient, MnemonicKey, MsgExecuteContract } = require("@xpla/xpla.js");

const lcd = new LCDClient({
chainID: 'cube_47-5',
URL: 'https://cube-lcd.xpla.dev'
});

const main = async () => {
const mk = new MnemonicKey({
mnemonic: 'myth snow ski simple century dad gun dolphin sail lawsuit fringe image toast betray frown keep harbor flash table prevent isolate panic tag vehicle'
})

const wallet = lcd.wallet(mk);
const myWalletAddress = wallet.key.accAddress;

const testnet_dezswap_factory = "xpla1j4kgjl6h4rt96uddtzdxdu39h0mhn4vrtydufdrk4uxxnrpsnw2qug2yx2";

const createPairMsg = new MsgExecuteContract(
myWalletAddress,
testnet_dezswap_factory,
{
"create_pair": {
"assets": [
{
"info": {
"native_token": {
"denom": "axpla"
}
},
"amount": "0"
},
{
"info": {
"token": {
"contract_addr": "xpla13mxqv7yqedcpm67fw7yz7p4s32rlsdkaae3ecmc0tram72ewefts2uu3ax" // Your Contract Address
}
},
"amount": "0"
}
]
}
}
);

const signedTx = await lcd.wallet(mk).createAndSignTx({
msgs: [createPairMsg]
});

const txResult = await lcd.tx.broadcastSync(signedTx);
console.log(txResult.txhash);
}
main()

위 코드를 실행하여 Testnet에서 Pair를 생성하였습니다. 트랜잭션 결과는 XPLA Explorer에서 확인할 수 있습니다. 여러분은 실행하실 때 contract_addr 항목에 여러분이 만든 CW20 토큰 컨트랙트 주소를 기입해야 합니다.

Event Logs를 확인해보면, 새로운 컨트랙트가 instantiate된 것을 알 수 있습니다. 이 중 code_id가 369인 컨트랙트는 dezswap_pair Contract이고, code_id가 110인 컨트랙트는 dezswap_token Contract입니다(code_id는 Testnet기준이며, Mainnet의 code_id는 다릅니다.). 예제에서 dezswap_pair Contract는 xpla1hzmcz38u28g6duy4gmur705e8qrdq7uvzsue4d8hdvclxz79uppsw3hfns, dezswap_token Contract는 xpla1ntr800lgjg59d79tr9c78kxjv9v9wm04hjdyq945663juf23hvqq0xy057입니다.

CW20-CW20끼리 교환하는 Pair를 만들고 싶다면 어떻게 해야 하나요?

위 코드에서 createPairMsg 변수를 아래와 같이 입력하면 됩니다.

const createPairMsg = new MsgExecuteContract(
myWalletAddress,
testnet_dezswap_factory,
{
"create_pair": {
"assets": [
{
"info": {
"token": {
"contract_addr": "xpla13mxqv7yqedcpm67fw7yz7p4s32rlsdkaae3ecmc0tram72ewefts2uu3ax" // Your Contract Address1
}
},
"amount": "0"
},
{
"info": {
"token": {
"contract_addr": "xpla13mxqv7yqedcpm67fw7yz7p4s32rlsdkaae3ecmc0tram72ewefts2uu3ax" // Your Contract Address2
}
},
"amount": "0"
}
]
}
}
);

2. Increase Allowance​

3. 유동성 공급 단계에서 CW20 토큰과 XPLA를 dezswap_pair Contract로 공급해주어야 합니다. 이때 dezpswap_pair Contract는 CW20 토큰의 TransferFrom 함수를 Execute하여, 토큰을 공급하는 지갑으로부터 CW20 토큰을 가져옵니다. 유동성 공급이 잘 이루어지기 위해, dezswap_pair Contract가 CW20 토큰을 가져갈 수 있도록 허용해주어야 합니다. 따라서 CW20 토큰 Contract의 IncreaseAllowance 함수를 실행해주겠습니다.

const { LCDClient, MnemonicKey, MsgExecuteContract } = require("@xpla/xpla.js");

const lcd = new LCDClient({
chainID: 'cube_47-5',
URL: 'https://cube-lcd.xpla.dev'
});

const main = async () => {
const mk = new MnemonicKey({
mnemonic: 'myth snow ski simple century dad gun dolphin sail lawsuit fringe image toast betray frown keep harbor flash table prevent isolate panic tag vehicle'
})

const wallet = lcd.wallet(mk);
const myWalletAddress = wallet.key.accAddress;

const cw20_contract_address = "xpla13mxqv7yqedcpm67fw7yz7p4s32rlsdkaae3ecmc0tram72ewefts2uu3ax"; // Your CW20 Contract Address

const increaseAllowanceMsg = new MsgExecuteContract(
myWalletAddress,
cw20_contract_address,
{
"increase_allowance": {
"amount": "10000000000",
"spender": "xpla1hzmcz38u28g6duy4gmur705e8qrdq7uvzsue4d8hdvclxz79uppsw3hfns" // dezswap_pair Contract Address
}
}
);

const signedTx = await lcd.wallet(mk).createAndSignTx({
msgs: [increaseAllowanceMsg]
});

const txResult = await lcd.tx.broadcastSync(signedTx);
console.log(txResult.txhash);
}
main()

위 코드를 실행한 결과는 XPLA Explorer에서 확인할 수 있습니다. 여러분은 실행하실 때 cw20_contract_address 변수에 여러분이 만든 CW20 토큰 컨트랙트 주소를 기입해야 하고, spender 항목에 여러분이 만든 dezpswap_pair 컨트랙트 주소를 넣어야 합니다. 예제에서는 amount 값을 10000000000으로 주었습니다. ACAD CW20토큰의 decimals 값이 6이므로, 최대 10,000개의 ACAD를 공급할 수 있습니다.

3. 유동성 공급​

1번에서 생성한 Pair에 유동성 공급을 위해 dezswap_pair Contract의 provide_liquidity 함수를 실행하겠습니다.

const { LCDClient, MnemonicKey, MsgExecuteContract, Coin } = require("@xpla/xpla.js");

const lcd = new LCDClient({
chainID: 'cube_47-5',
URL: 'https://cube-lcd.xpla.dev'
});

const main = async () => {
const mk = new MnemonicKey({
mnemonic: 'myth snow ski simple century dad gun dolphin sail lawsuit fringe image toast betray frown keep harbor flash table prevent isolate panic tag vehicle'
})

const wallet = lcd.wallet(mk);
const myWalletAddress = wallet.key.accAddress;

const dezswap_pair_contract_address = "xpla1hzmcz38u28g6duy4gmur705e8qrdq7uvzsue4d8hdvclxz79uppsw3hfns"; // dezswap_pair Contract Address

const provideLiquidityMsg = new MsgExecuteContract(
myWalletAddress,
dezswap_pair_contract_address,
{
"provide_liquidity": {
"assets": [
{
"info": {
"native_token": {
"denom": "axpla"
}
},
"amount": "10000000000000000000"
},
{
"info": {
"token": {
"contract_addr": "xpla13mxqv7yqedcpm67fw7yz7p4s32rlsdkaae3ecmc0tram72ewefts2uu3ax" // Your Contract Address
}
},
"amount": "10000000000"
}
]
}
},
[new Coin(
"axpla",
"10000000000000000000"
)],
);

const signedTx = await lcd.wallet(mk).createAndSignTx({
msgs: [provideLiquidityMsg],
});

const txResult = await lcd.tx.broadcastSync(signedTx);
console.log(txResult.txhash);
}
main()

위 코드를 실행한 결과는 XPLA Explorer에서 확인할 수 있습니다. XPLA와 CW20 토큰 각각 amount 항목에 공급하고 싶은 수량을 기입했습니다. 따라서 총 10 XPLA와 10,000 ACAD를 공급하였습니다.

provideLiquidityMsg 변수를 만들 때, 이전과는 달리 마지막 parameter로 axpla 값을 기입해주는 것이 중요합니다. 이를 통해 트랜잭션 실행과 동시에 dezswap_pair Contract에 XPLA를 공급할 수 있습니다. CW20 토큰 공급은 2. Increase allowance 단계에서 알아본 대로, dezswap_pair 컨트랙트가 CW20 토큰을 가져왔습니다.

4. Swap 진행 (Execute Swap)​

이제 Swap을 진행해보겠습니다. 예제에서는 ACAD 토큰 1개를 XPLA로 교환할 것입니다. Contract Execute를 위한 Msg 형식은 dezswap Github을 참고했습니다.

const { LCDClient, MnemonicKey, MsgExecuteContract } = require("@xpla/xpla.js");

const lcd = new LCDClient({
chainID: 'cube_47-5',
URL: 'https://cube-lcd.xpla.dev'
});

const main = async () => {
const mk = new MnemonicKey({
mnemonic: 'myth snow ski simple century dad gun dolphin sail lawsuit fringe image toast betray frown keep harbor flash table prevent isolate panic tag vehicle'
})

const wallet = lcd.wallet(mk);
const myWalletAddress = wallet.key.accAddress;

const token_contract_address = "xpla13mxqv7yqedcpm67fw7yz7p4s32rlsdkaae3ecmc0tram72ewefts2uu3ax"; // Your Contract Address

const swapMsg = new MsgExecuteContract(
myWalletAddress,
token_contract_address,
{
"send": {
"contract": "xpla1hzmcz38u28g6duy4gmur705e8qrdq7uvzsue4d8hdvclxz79uppsw3hfns", // dezswap_pair Contract Address
"amount": "1000000",
"msg": btoa(JSON.stringify({
"swap": {
// "belief_price": Option<Decimal>,
// "max_spread": Option<Decimal>,
// "to": Option<HumanAddr>
}
}))
}
}
);

const signedTx = await lcd.wallet(mk).createAndSignTx({
msgs: [swapMsg],
});

const txResult = await lcd.tx.broadcastSync(signedTx);
console.log(txResult.txhash);
}
main()

위 코드를 실행한 결과는 XPLA Explorer에서 확인할 수 있습니다. token_contract_address 변수에는 CW20 컨트랙트 주소를, msg 내에 contract 항목에는 dezswap_pair 컨트랙트 주소를 기입해야 합니다. XPLA를 CW20 토큰으로 교환할 때는 실행해야 하는 컨트랙트가 다르니 유의하셔야 합니다. 자세한 사항은 dezswap Github을 살펴보세요.

ACAD 토큰 1개를 넣었는데, 왜 XPLA를 약 0.0001개를 받았을까요? 3. 유동성 공급 단계에서 Pair에 총 10 XPLA와 10,000 ACAD를 공급하였습니다. 즉, Pair 내 토큰 개수의 곱은 항상 10 × 10000 = 100,000 만큼 유지되어야 합니다(CPMM 방식). ACAD 토큰 1개를 넣은 경우, Pair 내 토큰 개수 곱은 (10 - XPLA_out) × (10000 + 1) = 100,000이 되어야 합니다. 또한 dezswap에서는 swap을 할 때 유동성 공급을 해준 사람에게 LP Commission을 0.3%만큼 지급해야 합니다. 따라서 0.3%만큼 수수료를 냈을 때, 남은 XPLA 0.0001개를 받게 됩니다. 더 자세한 사항은 dezswap Docs를 참고해보세요.

XPLA를 CW20 토큰으로 교환하려면 어떻게 해야 하나요?

위 코드에서 swapMsg를 아래와 같이 입력하면 됩니다.

const swapMsg = new MsgExecuteContract(
myWalletAddress,
dezswap_pair_contract_address,
{
"swap": {
"offer_asset": {
"info": {
"native_token": {
"denom": "axpla"
}
},
"amount": "1000000000000000000"
},
//"belief_price": Option<Decimal>,
//"max_spread": Option<Decimal>,
//"to": Option<HumanAddr>
}
},
[new Coin(
"axpla",
"1000000000000000000"
)],
);

CW20 토큰을 XPLA로 교환할 때는 CW20 토큰 컨트랙트를 execute 했습니다. XPLA를 CW20 토큰으로 교환할 때는 반드시 dezswap_pair_contract_address를 execute 해야 합니다. 또, 컨트랙트 매개변수로 swap하고 싶은 XPLA 물량(new Coin("axpla", ".."))도 넣어줘야 합니다.

마무리​

지금까지 Dezswap 컨트랙트를 이용하여 Swap 기능을 개발해보았습니다. 여러분이 개발한 게임의 CW20 토큰과 XPLA를 Swap 컨트랙트를 통해 연결해보세요.