Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit ab5ea5c

Browse files
authored
Add support for native token (#112)
1 parent c60b4cc commit ab5ea5c

File tree

9 files changed

+595
-39
lines changed

9 files changed

+595
-39
lines changed

token/inc/token.h

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ typedef enum Token_TokenInstruction_Tag {
4444
*/
4545
InitializeMint,
4646
/**
47-
* Initializes a new account to hold tokens.
47+
* Initializes a new account to hold tokens. If this account is associated with the native mint
48+
* then the token balance of the initialized account will be equal to the amount of SOL in the account.
4849
*
4950
* The `InitializeAccount` instruction requires no signers and MUST be included within
5051
* the same Transaction as the system program's `CreateInstruction` that creates the account
@@ -75,7 +76,9 @@ typedef enum Token_TokenInstruction_Tag {
7576
*/
7677
InitializeMultisig,
7778
/**
78-
* Transfers tokens from one account to another either directly or via a delegate.
79+
* Transfers tokens from one account to another either directly or via a delegate. If this
80+
* account is associated with the native mint then equal amounts of SOL and Tokens will be
81+
* transferred to the destination account.
7982
*
8083
* Accounts expected by this instruction:
8184
*
@@ -141,7 +144,7 @@ typedef enum Token_TokenInstruction_Tag {
141144
*/
142145
SetOwner,
143146
/**
144-
* Mints new tokens to an account.
147+
* Mints new tokens to an account. The native mint does not support minting.
145148
*
146149
* Accounts expected by this instruction:
147150
*
@@ -158,7 +161,8 @@ typedef enum Token_TokenInstruction_Tag {
158161
*/
159162
MintTo,
160163
/**
161-
* Burns tokens by removing them from an account and thus circulation.
164+
* Burns tokens by removing them from an account. `Burn` does not support accounts
165+
* associated with the native mint, use `CloseAccount` instead.
162166
*
163167
* Accounts expected by this instruction:
164168
*
@@ -168,10 +172,28 @@ typedef enum Token_TokenInstruction_Tag {
168172
*
169173
* * Multisignature owner/delegate
170174
* 0. `[writable]` The account to burn from.
171-
* 1. `[]` The account's multisignature owner/delegate
175+
* 1. `[]` The account's multisignature owner/delegate.
172176
* 2. ..2+M '[signer]' M signer accounts.
173177
*/
174178
Burn,
179+
/**
180+
* Close an account by transferring all its SOL to the destination account.
181+
* Non-native accounts may only be closed if its token amount is zero.
182+
*
183+
* Accounts expected by this instruction:
184+
*
185+
* * Single owner/delegate
186+
* 0. `[writable]` The account to close.
187+
* 1. '[writable]' The destination account.
188+
* 2. `[signer]` The account's owner.
189+
*
190+
* * Multisignature owner/delegate
191+
* 0. `[writable]` The account to close.
192+
* 1. '[writable]' The destination account.
193+
* 2. `[]` The account's multisignature owner.
194+
* 3. ..3+M '[signer]' M signer accounts.
195+
*/
196+
CloseAccount,
175197
} Token_TokenInstruction_Tag;
176198

177199
typedef struct Token_InitializeMint_Body {
@@ -304,6 +326,10 @@ typedef struct Token_Account {
304326
* Is `true` if this structure has been initialized
305327
*/
306328
bool is_initialized;
329+
/**
330+
* Is this a native token
331+
*/
332+
bool is_native;
307333
/**
308334
* The amount delegated
309335
*/

token/js/cli/main.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import {
1414
failOnApproveOverspend,
1515
setOwner,
1616
mintTo,
17-
burn,
1817
multisig,
18+
burn,
19+
closeAccount,
20+
nativeToken,
1921
} from './token-test';
2022

2123
async function main() {
@@ -37,10 +39,14 @@ async function main() {
3739
await setOwner();
3840
console.log('Run test: mintTo');
3941
await mintTo();
40-
console.log('Run test: burn');
41-
await burn();
4242
console.log('Run test: multisig');
4343
await multisig();
44+
console.log('Run test: burn');
45+
await burn();
46+
console.log('Run test: closeAccount');
47+
await closeAccount();
48+
console.log('Run test: nativeToken');
49+
await nativeToken();
4450
console.log('Success\n');
4551
}
4652

token/js/cli/token-test.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,3 +387,75 @@ export async function multisig(): Promise<void> {
387387
assert(accountInfo.owner.equals(newOwner));
388388
}
389389
}
390+
391+
export async function closeAccount(): Promise<void> {
392+
const connection = await getConnection();
393+
const owner = new Account();
394+
const close = await testToken.createAccount(owner.publicKey);
395+
396+
let close_balance;
397+
let info = await connection.getAccountInfo(close);
398+
if (info != null) {
399+
close_balance = info.lamports;
400+
} else {
401+
throw new Error('Account not found');
402+
}
403+
404+
const balanceNeeded =
405+
await connection.getMinimumBalanceForRentExemption(0);
406+
const dest = await newAccountWithLamports(connection, balanceNeeded);
407+
408+
info = await connection.getAccountInfo(dest.publicKey);
409+
if (info != null) {
410+
assert(info.lamports == balanceNeeded);
411+
} else {
412+
throw new Error('Account not found');
413+
}
414+
415+
await testToken.closeAccount(close, dest.publicKey, owner, []);
416+
info = await connection.getAccountInfo(close);
417+
if (info != null) {
418+
throw new Error('Account not closed');
419+
}
420+
info = await connection.getAccountInfo(dest.publicKey);
421+
if (info != null) {
422+
assert(info.lamports == balanceNeeded + close_balance);
423+
} else {
424+
throw new Error('Account not found');
425+
}
426+
}
427+
428+
export async function nativeToken(): Promise<void> {
429+
const connection = await getConnection();
430+
431+
const mintPublicKey = new PublicKey('So11111111111111111111111111111111111111111');
432+
const payer = await newAccountWithLamports(connection, 100000000000 /* wag */);
433+
const token = new Token(connection, mintPublicKey, programId, payer);
434+
const owner = new Account();
435+
const native = await token.createAccount(owner.publicKey);
436+
let accountInfo = await token.getAccountInfo(native);
437+
assert(accountInfo.isNative);
438+
let balance;
439+
let info = await connection.getAccountInfo(native);
440+
if (info != null) {
441+
balance = info.lamports;
442+
} else {
443+
throw new Error('Account not found');
444+
}
445+
446+
const balanceNeeded =
447+
await connection.getMinimumBalanceForRentExemption(0);
448+
const dest = await newAccountWithLamports(connection, balanceNeeded);
449+
await token.closeAccount(native, dest.publicKey, owner, []);
450+
info = await connection.getAccountInfo(native);
451+
if (info != null) {
452+
throw new Error('Account not burned');
453+
}
454+
info = await connection.getAccountInfo(dest.publicKey);
455+
if (info != null) {
456+
assert(info.lamports == balanceNeeded + balance);
457+
} else {
458+
throw new Error('Account not found');
459+
}
460+
461+
}

token/js/client/token.js

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,16 @@ type MintInfo = {|
6161
* Owner of the mint, given authority to mint new tokens
6262
*/
6363
owner: null | PublicKey,
64+
6465
/**
6566
* Number of base 10 digits to the right of the decimal place
6667
*/
6768
decimals: number,
69+
6870
/**
6971
* Is this mint initialized
7072
*/
71-
initialized: Boolean,
73+
initialized: boolean,
7274
|};
7375

7476
const MintLayout = BufferLayout.struct([
@@ -107,6 +109,16 @@ type AccountInfo = {|
107109
* The amount of tokens the delegate authorized to the delegate
108110
*/
109111
delegatedAmount: TokenAmount,
112+
113+
/**
114+
* Is this account initialized
115+
*/
116+
isInitialized: boolean,
117+
118+
/**
119+
* Is this a native token account
120+
*/
121+
isNative: boolean,
110122
|};
111123

112124
/**
@@ -119,7 +131,7 @@ const AccountLayout = BufferLayout.struct([
119131
BufferLayout.u32('option'),
120132
Layout.publicKey('delegate'),
121133
BufferLayout.u8('is_initialized'),
122-
BufferLayout.u8('padding'),
134+
BufferLayout.u8('is_native'),
123135
BufferLayout.u16('padding'),
124136
Layout.uint64('delegatedAmount'),
125137
]);
@@ -139,6 +151,11 @@ type MultisigInfo = {|
139151
*/
140152
n: number,
141153

154+
/**
155+
* Is this mint initialized
156+
*/
157+
initialized: boolean,
158+
142159
/**
143160
* The signers
144161
*/
@@ -512,6 +529,8 @@ export class Token {
512529
accountInfo.mint = new PublicKey(accountInfo.mint);
513530
accountInfo.owner = new PublicKey(accountInfo.owner);
514531
accountInfo.amount = TokenAmount.fromBuffer(accountInfo.amount);
532+
accountInfo.isInitialized = accountInfo.isInitialized != 0;
533+
accountInfo.isNative = accountInfo.isNative != 0;
515534
if (accountInfo.option === 0) {
516535
accountInfo.delegate = null;
517536
accountInfo.delegatedAmount = new TokenAmount();
@@ -592,7 +611,7 @@ export class Token {
592611
signers = multiSigners;
593612
}
594613
return await sendAndConfirmTransaction(
595-
'transfer',
614+
'Transfer',
596615
this.connection,
597616
new Transaction().add(
598617
this.transferInstruction(
@@ -634,7 +653,7 @@ export class Token {
634653
signers = multiSigners;
635654
}
636655
await sendAndConfirmTransaction(
637-
'approve',
656+
'Approve',
638657
this.connection,
639658
new Transaction().add(
640659
this.approveInstruction(account, delegate, ownerPublicKey, multiSigners, amount),
@@ -666,7 +685,7 @@ export class Token {
666685
signers = multiSigners;
667686
}
668687
await sendAndConfirmTransaction(
669-
'revoke',
688+
'Revoke',
670689
this.connection,
671690
new Transaction().add(
672691
this.revokeInstruction(account, ownerPublicKey, multiSigners),
@@ -700,7 +719,7 @@ export class Token {
700719
signers = multiSigners;
701720
}
702721
await sendAndConfirmTransaction(
703-
'setOwneer',
722+
'SetOwner',
704723
this.connection,
705724
new Transaction().add(
706725
this.setOwnerInstruction(owned, newOwner, ownerPublicKey, multiSigners),
@@ -735,7 +754,7 @@ export class Token {
735754
signers = multiSigners;
736755
}
737756
await sendAndConfirmTransaction(
738-
'mintTo',
757+
'MintTo',
739758
this.connection,
740759
new Transaction().add(this.mintToInstruction(dest, ownerPublicKey, multiSigners, amount)),
741760
this.payer,
@@ -767,14 +786,45 @@ export class Token {
767786
signers = multiSigners;
768787
}
769788
await sendAndConfirmTransaction(
770-
'burn',
789+
'Burn',
771790
this.connection,
772791
new Transaction().add(this.burnInstruction(account, ownerPublicKey, multiSigners, amount)),
773792
this.payer,
774793
...signers,
775794
);
776795
}
777796

797+
/**
798+
* Burn account
799+
*
800+
* @param account Account to burn
801+
* @param authority account owner
802+
* @param multiSigners Signing accounts if `owner` is a multiSig
803+
*/
804+
async closeAccount(
805+
account: PublicKey,
806+
dest: PublicKey,
807+
owner: Account | PublicKey,
808+
multiSigners: Array<Account>,
809+
): Promise<void> {
810+
let ownerPublicKey;
811+
let signers;
812+
if (owner instanceof Account) {
813+
ownerPublicKey = owner.publicKey;
814+
signers = [owner];
815+
} else {
816+
ownerPublicKey = owner;
817+
signers = multiSigners;
818+
}
819+
await sendAndConfirmTransaction(
820+
'CloseAccount',
821+
this.connection,
822+
new Transaction().add(this.closeAccountInstruction(account, dest, ownerPublicKey, multiSigners)),
823+
this.payer,
824+
...signers,
825+
);
826+
}
827+
778828
/**
779829
* Construct a Transfer instruction
780830
*
@@ -1044,4 +1094,44 @@ export class Token {
10441094
data,
10451095
});
10461096
}
1097+
1098+
/**
1099+
* Construct a Burn instruction
1100+
*
1101+
* @param account Account to burn tokens from
1102+
* @param owner account owner
1103+
* @param multiSigners Signing accounts if `owner` is a multiSig
1104+
*/
1105+
closeAccountInstruction(
1106+
account: PublicKey,
1107+
dest: PublicKey,
1108+
owner: Account | PublicKey,
1109+
multiSigners: Array<Account>,
1110+
): TransactionInstruction {
1111+
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]);
1112+
const data = Buffer.alloc(dataLayout.span);
1113+
dataLayout.encode(
1114+
{
1115+
instruction: 9, // CloseAccount instruction
1116+
},
1117+
data,
1118+
);
1119+
1120+
let keys = [
1121+
{pubkey: account, isSigner: false, isWritable: true},
1122+
{pubkey: dest, isSigner: false, isWritable: true},
1123+
];
1124+
if (owner instanceof Account) {
1125+
keys.push({pubkey: owner.publicKey, isSigner: true, isWritable: false});
1126+
} else {
1127+
keys.push({pubkey: owner, isSigner: false, isWritable: false});
1128+
multiSigners.forEach(signer => keys.push({pubkey: signer.publicKey, isSigner: true, isWritable: false}));
1129+
}
1130+
1131+
return new TransactionInstruction({
1132+
keys,
1133+
programId: this.programId,
1134+
data,
1135+
});
1136+
}
10471137
}

0 commit comments

Comments
 (0)